react-setState update mechanism-source code analysis

setState asynchronous update

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 call stack

既然 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); 
 } 
 }, 
} 

If isBatchingUpdates 为 false, then perform all updates in the queue batchedUpdatesmethod, otherwise only
the current component (ie, call the setState 的components) into the dirtyComponentsarray.


**Note--------** The original text here is if isBatchingUpdates 为 true, but from the code point of view, the update method should be executed when the batch is updated. If it is a batch update, it will enter the dirtyqueue.
So I think it is:

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

Insert picture description here

Insert picture description here
4. In summary

  • Asynchronous execution under synchronous code
  • Synchronous execution under asynchronous code

Asynchronous execution under synchronous code

Under non-event callbacks and setTimeout, such as the life cycle of react , the code of setState is executed asynchronously.

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。

Another important point is that multiple asynchronous setStates will be combined and executed, so componentDidUpdate will only be executed once, and it is 1.

Synchronous execution of asynchronous code

If your setState is wrapped by setTimeout, or the callback fn in the event function, then setState will be executed synchronously . And it will not be merged and executed , which will cause the component to be rendered multiple times.

  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);
  }

So the execution result is

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

setState is executed synchronously, and the component is re-rendered every time.

Source explanation because a callback function has been executed asynchronously after the transaction close, and this time isBatchingUpdatesalready false, and it will be updated directly. So it is synchronous execution.


The above is a summary of my own learning process, and share it with everyone to learn together.
Partially summarized from "Deep into the React Technology Stack"
Insert picture description here

Guess you like

Origin blog.csdn.net/weixin_45416217/article/details/112424751