共计 4468 个字符,预计需要花费 12 分钟才能阅读完成。
<!– TOC –>
- Q1–setState 改变状态之后,不会立即更新 state 值
-
A1
- [1. react 生命周期]()
- 2. react 更新 state 具体做了什么
- 3. transaction 事务
- 4. 回顾上述问题
- 5. 总结
- Q2– 在 render 函数里,无法 setState
- A2
<!– /TOC –>
Q1
setState 改变状态之后,不会立即更新 state 值。所以,如果改变 state 值,react 是什么时候进行组件的更新呢?setState()到底做了一些什么呢?
A1
1. react 生命周期
2. react 更新 state 具体做了什么
引入一段源码
react 中定义的 setState 方法,定义了两个参数(partialState,callback)。
partialState: 新的 state 值;
callback: 回调函数。
getInternalInstanceReadyForUpdate 方法的目的是获取当前组件对象,将其赋值给 internalInstance 变量。接下来判断当前组件对象的 state 更新队列是否存在,如果存在则将 partialState 也就是新的 state 值加入队列;如果不存在,则创建该对象的更新队列。然后进入 enqueueUpdate 方法。
enqueueCallback 也是先获取当前组件对象,如果已经存在其他回调,就加入等待回调队列,如果当前没有回调,就创建等待回调队列。然后进入 enqueueUpdate 方法。
可以发现,enqueueSetState&enqueueCallback 最终都是进入 enqueueUpdate 方法。下面我们来看看 enqueueUpdate 方法。
官方注解是:给组件做个标记:需要重新渲染,并且将可选的回调函数添加到函数列表中,这些函数将在重新渲染的时候执行。
我们看一下函数具体做了哪些事。发现这个函数只是做了一个判断: 如果 batchingStrategy.isBatchingUpdates 为 false, 就执行 batchingStrategy.batchedUpdates(enqueueUpdate,component),否则就加入 dirtyComponents。
这里提到 batchingStrategy,批量更新策略。
批量更新策略是什么呢?看代码发现 batchingStrategy 批量更新策略只是一个简单的对象,定义了一个 isBatchingUpdates 的布尔值和一个 batchedUpdates 方法。默认 isBatchingUpdates(下面称为更新标志)为 false, 然后会进入 batchedUpdates 方法,先把更新标志 isBatchingUpdates 设为 true,然后执行 transaction.perform(callback),即 transaction.perform(enqueueUpdate)。
React 内部采用了 ” 状态机 ” 的概念,组件处于不同的状态时,所执行的逻辑也并不相同。以组件更新流程为例,React 以事务 + 状态的形式对组件进行更新。
通过上面的一部分代码,我们发现 setState()方法主要是 enqueueUpdate()进行状态更新,怎样进行状态更新呢?定义了一个批量更新策略:判断更新标志 isBatchingUpdates 的值,如果为 false,调用 batchedUpdates()–>(先把更新标志 isBatchingUpdates 改为 true,然后调用 transaction.perform(enqueueUpdate))。如果为 true,就把组件加入 dirtyComponents 数组中。
React 内部采用了 ” 状态机 ” 的概念,组件处于不同的状态时,所执行的逻辑也并不相同。以组件更新流程为例,React 以事务 + 状态的形式对组件进行更新,因此接下来我们看看事务的机制。
3. transaction 事务
wrappers (injected at creation time)
+ +
| |
+-----------------|--------|--------------+
| v | |
| +---------------+ | |
| +--| wrapper1 |---|----+ |
| | +---------------+ v | |
| | +-------------+ | |
| | +----| wrapper2 |--------+ |
| | | +-------------+ | | |
| | | | | |
| v v v v | wrapper
| +---+ +---+ +---------+ +---+ +---+ | invariants
perform(anyMethod) | | | | | | | | | | | | maintained
+----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
| | | | | | | | | | | |
| | | | | | | | | | | |
| | | | | | | | | | | |
| +---+ +---+ +---------+ +---+ +---+ |
| initialize close |
+-----------------------------------------+
这是官方代码的解析图。
可以看出调用函数是 perform(anyMethod), 然后方法 anyMethod 被 wrapper 包裹了,wrapper 依次执行了 initialize->anyMethod->close
function anyMethod(){console.log('xx')
};
transaction.perform(anyMethod);
代码的执行顺序是
initialize()
输出 xx
close()
所以这里 wrapper 是怎样定义的呢?
第二个 wrapper 比较简单,先来看一下第二个 wrapper。
第二个 wrapper(RESET_BATCHED_UPDATES)的作用是将更新标志 isBatchingUpdates 重置为 false; 我的理解这里是收集完所有要更新的 state 值,都加入_pendingStateQueue 待更新状态队列了,然后组件更新完了之后,将更新标志重置为 false,等待下次更新。然后下面来看一下第一个 wrapper。
5&f=png&s=54081)
第一个 wrapper 主要的作用是更新组件,执行了 ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)。
可以看到 flushBatchedUpdates 方法循环遍历所有的 dirtyComponents,又通过事务的形式调用 runBatchedUpdates 方法。
一共做了两件事:
- 一是通过执行 updateComponent 方法来更新组件
- 二是若 setState 方法传入了回调函数则将回调函数存入 callbackQueue 队列。
然后看一下 updateComponent 方法, 官方注释是:更新组件,会调用 shouldComponentUpdate, 然后调用剩余的生命周期函数,更新 DOM 结构。
这里终于更新了组件。看代码会发现在 shouldComponentUpdate 之前,执行了 _processPendingState 方法, 该方法主要对 state 进行处理:
- 1. 如果更新队列为 null,那么返回原来的 state;
- 2. 如果更新队列有一个更新,那么返回更新值;
- 3. 如果更新队列有多个更新,那么通过 for 循环将它们合并;
综上说明了,在一个生命周期内,在 componentShouldUpdate 执行之前,所有的 state 变化都会被合并,最后统一处理。
4. 回顾上述问题
综上,
- setState()为啥没有立即更新 this.state 值呢
- 如果在 componentDidMount()中连续多次 setState, 无法进行 state 累加呢
- 批量更新策略 isBatchingStrategy 干了什么,怎么做到更新的呢
那按照上述说的批量更新,第一次 setState–> 进入 enqueueUpdate()–> 此时 isBatchingUpdates 默认为 false–>batchedUpdates(enqueueUpdate,…)–> 设置 isBatchingUpdates 为 true;transaction.perform(enqueueUpdates);–>(第一个 wrapper:FLUSH_BATCHED_UPDATES)组件更新 –>(第二个 wrapper:RESET_BATCHED_UPDATES 的 close 方法)设置 isBatchingUpdates 为 false–>第二次 setState–>isBatchingUpdates 为 false–>..–> 组件更新 –>isBatchingUpdates 恢复为 false。
这样和结果不对呀?按上述逻辑的话,岂不是每次 setState 都会更新 this.state 的值?
调试代码会发现,原来整个将 React 组件渲染到 DOM 中的过程就处于一个大的 Transaction 中。
在进入生命周期之前,就会调用 batchedUpdates(),所以此时 isBatchingUpdates 已经修改为 true 了。后面第一次进入 setState()时,就会进入加入 dirtyComponent 中。所以这也就是为什么两次打印 this.state.foods 都是 ” 的原因,新的 state 还没有被应用到组件中。
5. 总结
- setState(partialState, callback),不会立即更新 state 值,要合并所有的 state 变化后,然后重新渲染的时候,state 值才会更新。
- setState(partialState, callback): callback 会在所有状态更新之后再调用(demo 中 state 的 foods&drinks 全部更新之后才会调用)
- 事务这么有用,那我们可以调用事务吗?答案是不可以。
- 另外在 componentWillMount 里面 setState()不会触发重新渲染
Q2
在 render 函数里,无法 setState
A2
在 render 函数中不能 setState()。
从 react 生命周期可以看出:state 更新会重新触发 render(),所以会导致 setState()–>re-render()–>setState()–re-render()–>…–>setState()–>re-render(),一直循环往复。
所以,同理在 state 更新的生命周期的函数中(componentWillUpdate/componentDidUpdate),都不能 setState()
参考资料
https://juejin.im/post/59cc4c…
https://zh-hans.reactjs.org/d…
https://www.imooc.com/article…
https://segmentfault.com/a/11…