每次使用setState更新属性值,都会再次执行函数组件,并将函数返回的虚拟DOM与虚拟DOM树进行一个DOM Diff的操作。
注意
React每次渲染都会执行函数组件,那函数组件中的React.useState(0)每次拿到的值都是0吗。答案很明显当然不是。
那么React。useState(0)在每次函数组件执行的时候都会调用,而得到却并非是初始值0。也就是说相同的代码在不同次数执行的时候得到的值是不一样的。
那么React是如何实现这个功能的呢?
实现React.useState
下面我们试着简单实现一下useState
第一版
let _state
const myUseState=(initialize)=>{
_state = _state===undefined? initialize:_state;
const setState=(newState)=>{
_state = newState;
render(); //每次更新都会重新渲染
}
return [_state,setState]
}
const render=()=>{
ReactDOM.render(
<React.StrictMode>
<Component />
</React.StrictMode>,
document.getElementById('root')
)
}
测试代码:
const Component = () => {
const [n,setN] = myUseState(0)
const [m,setM] = myUseState(0)
return (
<>
<button onClick={()=>setN(n+1);console.log('点击了n')}>+1</button>
<div>n: {n}</div>
<button onClick={()=>setM(m+1);console.log('点击了m')}>+1</button>
<div>m: {m}</div>
</>
)
}
在只有当个值的时候是可以正常运行的,但是如果函数组件中多次使用useState,例如代码使用两次myUseState就会因为使用了同一个全局变量保存数据,引发m和n的值同步的现象。
第二版
那我们应该使用什么数据结构来保存呢?对象?数组?
其实使用对象来保存数据是不可行的,因为对象是无序的你如果需要获取某个值就必须要有一个key,显然在使用React.useState时并没有传入key。
那换数组呢?答案是可行的,通过数组的特性顺序存取每个State的值,但这会引发一个问题后面会讲到。
在第一版的基础上添加数组来保存每个不同的值,以及下标。
+ let _state=[];
+ let index = 0
const render=()=>{
+ index = 0 // render在前需要将索引清零
ReactDOM.render(
<React.StrictMode>
<Component />
</React.StrictMode>,
document.getElementById('root')
)
}
const myUseState=(initialize)=>{
let currentIndex = index //由于index需要自增这里保存一下index临时变量
_state[currentIndex] = _state[currentIndex]===undefined? initialize:_state[currentIndex];
const setState=(newState)=>{
_state[currentIndex] = newState;
render();
}
index++
return [_state[currentIndex],setState]
}
这就完成了一个有基本功能的useState
React会将_state和index全局变量放到虚拟DOM中。也就是每个虚拟DOM对象都对应有自己的state数组,这样就不会导致函数组件产生冲突。
图中展示了首次执行App函数组件会通过useState将数组_state和索引index保存到返回的虚拟DOM中。当使用setN之后改变了n的值,React就会通过DOM Diff检测出变化,并使用Patch更新虚拟DOM树中的_state数组下标为index的值。
React中_state真实的名字应该叫做memorizeState,index的实现则使用了链表。
useState数组中保存的顺序非常重要
在执行函数组件的时候可以通过下标的自增获取对应的state值。由于是通过顺序获取的,这将会强制要求你不允许更改useState的顺序,例如使用条件判断是否执行useState这样会导致按顺序获取到的值与预期的值不同。这个问题也出现在了React.useState自己身上。
因此React是不允许你使用条件判断去控制函数组件中的useState的顺序的。这会导致获取到的值混乱。
如下代码React会直接报错
const Component =()=>{
let state
if(true){
state= React.useState(0);
}
return (
<div>{state}</div>
)
}
我想你之后看到类似报错就不会有疑惑了
Vue3借鉴了React的Hook思想,并解决了这个问题。
使用回调函数来设置state
由于setState并不是立即生效的,多次使用setState只会执行最后一次的修改。
方案一
把多有操作都和为一次
方按二
使用回到函数,参数接受执行函数时count的值
const [count,setCount] = useState(0)
useEffect(()=>{
setCount((c)=>c+1)
setCount((c)=>c+1)
})
推荐都使用过回调函数的方式设置state
总结
React.useState通过线性的数据结构存储,并构按函数组件中Raect.useState执行的顺序存储state
存在的问题: 在函数组件中不能使用条件判断去改变是否执行React.useState,React会直接报错,因为这种操作会导致顺序state的顺序出现混乱。