正确地使用 State

关于 setState() 你应该了解三件事:

1.不要直接修改 State

直接修改State不会重新渲染组件:

// Wrong
this.state.comment = 'Hello';

而是应该使用 setState():

// Correct
this.setState({comment: 'Hello'});

构造函数是唯一可以给 this.state 赋值的地方。

setState函数定义在Component的原型对象上,

image-20220703223939808

react -> ReactBaseClasses.js -> Component

2.setState异步更新

特殊情况下:setState是同步更新的

为什么setState要设计成异步更新?

  • 首先,setState设计成异步,可以显著提升性能
    • 如果每次调用setState都进行一次更新,那么意味着render函数会被频繁的调用,界面重新渲染,这样效率很低。
    • 最好的办法是:获得多个更新,之后进行批量处理。
  • 如果同步更新了State, 但是还没有执行render函数,那么state和props不能保持同步
    • 比如:父组件的state通过props传递给子组件,父组件改变State触发更新,但是此时传递给子组件的props还是上一次的值,这可能产生一些问题。

如何拿到最新的State状态?

有两种方式:

  • 通过setState的第二个参数,传入一个回调函数callback,这个回调函数会在state状态更新后执行。

  • 在componentDidUpdate生命周期函数中,获取最新的State的值。

源代码中是先执行生命周期函数,再执行回调的

有什么方法可以让setState同步更新?

有两种情况下,setState是同步更新的

  • 将setState放入到异步方法中,如:setTimeout
setTimeout(() => {
this.setState({
newState
})
}, 0);
  • 在原生的dom事件中,如点击事件。

所以setState的更新方式分为两种情况:

  • 在组件生命周期或React合成事件中,setState是异步的。
  • 在setTimeout或者原生dom事件中,setState是同步的。

源码

​ 从上面的setState源码可以看到,setState方法其实调用的是this.updater.enqueueSetState方法

image-20220703230810414

这个updater是传递个每一个Class组件的参数。

它在源码中react-reconciler 这个包下面的ReactFiberClassComponent.old.js中

image-20220703231529957

可以看到这个updater中有一个requestUpdateLane方法,这个方法,会根据当前的运行上下文,计算本次update的优先级。

image-20220703231845841

之后requestUpdateLane函数会将优先级返回,赋值给lane, 之后createUpdate函数会根据这个lane创建一个update。

后面的 enqueueUpdate(fiber, update);方法会将这个update挂载到对应的Fiber节点上

image-20220703232433183

在将update挂载到对应的Febier节点后,就通过scheduleUpdateOnFiber调度本次更新。

image-20220703232616801


参考: