react-setState更新机制--源码解析
发布日期:2021-05-20 10:06:59 浏览次数:33 分类:技术文章

本文共 3936 字,大约阅读时间需要 13 分钟。

setState 异步更新

React 初学者常会写出 this.state.value = 1 这样的代码,这是完全错误的写法。注意 绝对不要直接修改 this.state,这不仅是一种低效的做法,而且很有可能会被之后的操作替换。setState 通过一个队列机制实现 state 更新。当执行 setState 时,会将需要更新的 state 合并后放入**状态队列**,而不会立刻更新 this.state,队列机制可以高效地批量更新 state。如果不通过setState 而直接修改 this.state 的值,那么该 state 将不会被放入状态队列中,当下次调用setState 并对状态队列进行合并时,将会忽略之前直接被修改的 state,而造成无法预知的错误。因此,应该使用 setState 方法来更新 state,同时 React 也正是利用状态队列机制实现了 setState的异步更新,避免频繁地重复更新 state。

setState 调用栈

既然 setState 最终是通过 enqueueUpdate 执行 state 更新,那么 enqueueUpdate 到底是如何更新 state 的呢?首先,看看下面这个问题,你是否能够正确回答呢?import React, {
Component } from 'react'; class Example extends Component {
constructor() {
super(); this.state = {
val: 0 }; } componentDidMount() {
this.setState({
val: this.state.val + 1}); console.log(this.state.val); // 第 1 次输出 this.setState({
val: this.state.val + 1}); console.log(this.state.val); // 第 2 次输出 setTimeout(() => {
this.setState({
val: this.state.val + 1}); console.log(this.state.val); // 第 3 次输出 this.setState({
val: this.state.val + 1}); console.log(this.state.val); // 第 4 次输出 }, 0); } render() {
return null; } } 上述代码中, 4 次 console.log 打印出来的 val 分别是:0、0、2、3。假如结果与你心中的答案不完全相同,那么你应该会感兴趣 enqueueUpdate 到底做了什么?function enqueueUpdate(component) {
ensureInjected(); // 如果不处于批量更新模式 if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component); return; } // 如果处于批量更新模式,则将该组件保存在 dirtyComponents 中 dirtyComponents.push(component); } 如果 isBatchingUpdates 为 false,则对所有队列中的更新执行 batchedUpdates 方法,否则只把当前组件(即调用了 setState 的组件)放入 dirtyComponents 数组中。例子中 4 次 setState 调用的表现之所以不同,这里逻辑判断起了关键作用。那 么 batchingStrategy 究竟做什么呢?其实它只是一个简单的对象,定义了一个isBatchingUpdates 的布尔值,以及 batchedUpdates 方法 var ReactDefaultBatchingStrategy = {
isBatchingUpdates: false, batchedUpdates: function(callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates; ReactDefaultBatchingStrategy.isBatchingUpdates = true; if (alreadyBatchingUpdates) {
callback(a, b, c, d, e); } else {
transaction.perform(callback, null, a, b, c, d, e); } }, }

如果 isBatchingUpdates 为 false,则对所有队列中的更新执行 batchedUpdates 方法,否则只

把当前组件(即调用了 setState 的组件)放入 dirtyComponents 数组中。


**注意--------**这里原文写的是如果 isBatchingUpdates 为 true,但从代码来看,应该是不是批量更新的时候,才执行更新方法,是批量更新的话,进入dirty队列中。

所以我觉得是:

如果 `isBatchingUpdates 为 false`,则对所有队列中的更新执行 `batchedUpdates` 方法,否则只把当前组件(即调用了 `setState 的`组件)放入 `dirtyComponents` 数组中。

在这里插入图片描述

在这里插入图片描述

4、总结来说

  • 同步代码下异步执行
  • 异步代码下同步执行

同步代码下异步执行

在非事件回调和setTimeout下,比如react的生命周期中,setState 的代码是异步执行的。

constructor() {
super(); this.state = {
val: 0, }; } componentDidMount() {
console.log(this.state.val, '1'); // 0 this.setState({
val: this.state.val + 1, }); this.setState({
val: this.state.val + 1, }); console.log(this.state.val, '2'); // 0 console.log(this.state.val, '3'); // 0 } componentDidUpdate() {
console.log('did'); console.log(this.state.val); }
原因是:react源码中有一个 `isBatchingUpdates: false`,在每个事务开始前会被置为 true,事务结束后的close会被再置回false;isBatchingUpdates 为 **true**,所以并不会直接执行更新 state,而是加入了 dirtyComponents,,等待后续执行,所以此时会被异步挂起。所以打印时获取的都是更新前的状态 0。

还有一个重点,多个异步的setState 会被合并执行,所以componentDidUpdate 只会被执行一次,且是 1。

异步代码同步执行

如果你的setState是被setTimeout包裹的,或者是事件函数中的回调fn,那setState就会被同步的执行。且不会被合并执行,会导致组件被渲染多次。

componentDidMount() {
setTimeout(() => {
console.log(this.state.val, '1'); this.setState({
val: this.state.val + 1, }); this.setState({
val: this.state.val + 1, }); console.log(this.state.val, '2'); console.log(this.state.val, '3'); }, 0); } componentDidUpdate() {
console.log('did'); console.log(this.state.val); }

所以执行结果就是

0 "1"did1did22 "did"2 "did"

setState 同步执行,且每次都会重新render组件。

源码解释 因为 回调函数异步执行 已经是在事务close之后了,这个时候 isBatchingUpdates 已经是false 了,所以会直接更新。所以就是同步执行。


以上都是自己学习过程中的汇总,分享出来大家一起学习。

部分总结自《深入React技术栈》
在这里插入图片描述

转载地址:https://blog.csdn.net/weixin_45416217/article/details/112424751 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:React 合成事件与 JavaScript 原生事件对比
下一篇:关于For循环中将let替换成var的原因,我觉得这是最好的回答

发表评论

最新留言

能坚持,总会有不一样的收获!
[***.219.124.196]2024年04月24日 13时57分47秒