react-setState更新机制--源码解析

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 分别是:0023。
假如结果与你心中的答案不完全相同,那么你应该会感兴趣 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"
did
1
did
2
2 "did"
2 "did"

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

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


以上都是自己学习过程中的汇总,分享出来大家一起学习。
部分总结自《深入React技术栈》
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_45416217/article/details/112424751