react hook当中提供了useState钩子函数,能够实现在函数当中创建状态和改变状态的方法,并支持多次调用。下面来一步步实现一个简易的useState。
首先,根据useState的用法,调用时传入一个初始值,并返回一个数组,第一项为创建的状态,第二项为更改该状态的方法。
const [count, setCount] = useState(0)
因此,我们先根据这个要求声明一个满足上述条件的函数:
let state
function useState(initialState) {
state = state ? state : initialState
function setState(newState) {
state = newState
render()
}
return [state, setState]
}
// 因为状态更改要刷新视图,因此这里用ReactDom.render方法来模拟更改状态后刷新视图的操作
function render() {
ReactDom.render(<App />, document.getElementById('root'))
}
说明一下,这里state为什么不声明在函数内部而是外部呢?因为函数组件每当状态改变都会重新调用,如果声明在函数内部,在重新调用时就无法缓存住上次更改后的状态,又会重新被设置为初始值,因此需要声明在外部,在状态更改函数重新调用时依然可以缓存上次修改的状态值。
上述代码中的useState函数可以实现创建并修改状态,而且可以在状态修改后自动刷新视图。但是还不支持多次调用声明多个状态。很显然,一个state是无法缓存多个不同的状态值的,如果想要存储多个状态值,并且能和修改状态的方法一一对应,最好的办法就是将state和setState都声明为数组,将内部的值按照索引来对应存储。
// 存储状态的数组
let state = []
// 存储更改状态方法的数组
let setters = []
// 用来记录状态和更改状态方法对应关系的下标
let stateIndex = 0
function createSetter(index) {
return function (newState) {
state[index] = newState
render()
}
}
function useState(initialState) {
state[stateIndex] = state[stateIndex] ? state[stateIndex] : initialState
// 采用闭包缓存每个state对应的setState
setters.push(createSetter(stateIndex))
const value = state[stateIndex]
const setter = setters[stateIndex]
// 每创建完一组都要+1,用来作为下一组状态的索引
stateIndex++
return [value, setter]
}
// 因为状态更改要刷新视图,因此这里用ReactDom.render方法来模拟更改状态后刷新视图的操作
function render() {
// 每次调用render都要重置stateIndex,否则对应的索引无限递增将无法正确匹配state和setState之间的关系
stateIndex = 0
ReactDom.render(<App />, document.getElementById('root'))
}
以上,useState的基本功能就已经实现了。将其代替react中的useState引入组件可看到效果。
这里的全局只是模拟,在react中,每个函数的状态都是作为函数的属性而存在的,并非全局。这样就不会造成变量污染,并且会跟随函数的声明周期创建和销毁。