简单实现
首先捋一下state更新的过程
function App(){
const [n,setN]=React.useState(0)
return(
<div className="App">
...
</div>
)
}
ReactDOM.render(<App/>,root)
<App/>
相当于App()
当首次渲染时,调用render
,render
中执行App()
,得到了虚拟dom
,之后再创建真实dom
元素。
当用户点击按钮调用setN
时,会再次render,并再次调用App(),得到虚拟dom,再dom diff更新dom
可是每次调用,都会执行useState(0),岂不是每次都重置了吗?
可以想到的是组件应该维护着一个state,每次都把最新的值存进去,
let _state;
const myUseState=initialValue=>{
_state=_state===undefined?initialValue:_state
const setState=newValue=>{
_state=newValue
render()
}
return [_state,setState]
}
const render=()=>{
ReactDOM.render(
<Test/>,
document.getElementById("root")
);
}
function Test(){
const [state,setState]=myUseState(0)
function add(){
setState(state+1)
}
return (
<div>
<button onClick={()=>{add()}}>+1</button>
{state}
</div>
)
}
但是,当有多个数据时,需要一个数组来保存
let _state=[];
let index=0
const myUseState=initialValue=>{
const currentIndex=index
_state[currentIndex]=_state[currentIndex]===undefined?
initialValue:_state[currentIndex]
const setState=newValue=>{
_state[currentIndex]=newValue
render()
}
index+=1
return [_state[currentIndex],setState]
}
const render=()=>{
index=0
ReactDOM.render(
<Test/>,
document.getElementById("root")
);
}
function Test(){
const [n,setN]=myUseState(0)
const [m,setM]=myUseState(0)
function addN(){
setN(n+1)
}
function addM(){
setM(m+1)
}
return (
<div>
<button onClick={()=>{addN()}}>+1</button>
{n}
<button onClick={()=>{addM()}}>+1</button>
{m}
</div>
)
}
这样就实现了多个数据的状态管理
重点是useState维护着一个数组,按顺序存放着声明的所有数据。因为数组是按顺序 排列,所以每次useState内部调用render时都要重置index,每个组件都有一个自己的_state和index
useState的顺序限制
但是正因为数据都按顺序排列,如果在if中调用useState就会出问题
const [n,setN]=React.useState(0)
if(n%2===0){
const [m,setM]=React.useState(0)
}
const [k,setK]=React.useState(0)
这样用就会报错,提示必须在完全相同的顺序下调用hook
源码简析
在源码中,每一个react节点都对应一个FiberNode,里面存放着一个Fiber对象,这个对象以链表的形式记录了数据,就像上文中的_state数组一样
每次调用useState
后,设置在memoizedState
上的Hook对象是这样:
{
baseState,
next,
baseUpdate,
queue,
memoizedState
}
每个在FunctionalComponent
中调用的useState
都会有一个对应的Hook对象,他们按照执行的顺序以类似链表的数据格式存放在Fiber.memoizedState
上
memoizedState
用来记录应该返回的结果,而next
指向下一次使用useState
对应的对象
setState
,其实是useState
的第二个返回方法dispatchAction
,执行时会创建一个:
var update = {
expirationTime: _expirationTime,
action: action,
callback: callback !== undefined ? callback : null,
next: null
}
这个update
会被加入到queue队列中,可能有多次setState
,react
会在收集完update
后,调度一次react
更新,这会执行一次FunctionalComponent
,就会执行对应的useState
,拿到Hook
对象,它保存了queue
对象表示有哪些更新存在,依次执行更新,拿到最新的state
保存到memoizedState
上并返回,最终达到setState
的效果