Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

第 18 题:React 中 setState 什么时候是同步的,什么时候是异步的? #17

Open
sisterAn opened this issue Feb 21, 2019 · 31 comments
Labels

Comments

@sisterAn
Copy link
Collaborator

sisterAn commented Feb 21, 2019

在React中,如果是由React引发的事件处理(比如通过onClick引发的事件处理),调用setState不会同步更新this.state,除此之外的setState调用会同步执行this.state 。所谓“除此之外”,指的是绕过React通过addEventListener直接添加的事件处理函数,还有通过setTimeout/setInterval产生的异步调用。

原因: 在React的setState函数实现中,会根据一个变量isBatchingUpdates判断是直接更新this.state还是放到队列中回头再说,而isBatchingUpdates默认是false,也就表示setState会同步更新this.state,但是,有一个函数batchedUpdates,这个函数会把isBatchingUpdates修改为true,而当React在调用事件处理函数之前就会调用这个batchedUpdates,造成的后果,就是由React控制的事件处理过程setState不会同步更新this.state

注意: setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。

详细请看 深入 setState 机制

@azl397985856
Copy link

这里所说的同步异步, 并不是真正的同步异步, 它还是同步执行的。

这里的异步指的是多个state会合成到一起进行批量更新。

希望初学者不要被误导

@azl397985856
Copy link

azl397985856 commented Feb 22, 2019

这里所说的同步异步, 并不是真正的同步异步, 它还是同步执行的。
这里的异步指的是多个state会合成到一起进行批量更新。
希望初学者不要被误导

你的说法还是太片面了,可以看下官网的介绍

image
https://reactjs.org/docs/faq-state.html#when-is-setstate-asynchronous

你是想说明什么,没太明白。

你的意思难道是从你截图给的这个可以看出reactsetState是或者部分是异步的?

---补充---

或者你的意思难道是标题是《何时setState是异步的》, 就断定 “setState确实可能是异步的”

@xueqingxiao
Copy link

这道题和19题有些类似。#18 (comment)

@Lizhooh
Copy link

Lizhooh commented Feb 23, 2019

我们平时写的时候就当它是异步进行就好了。

@yygmind yygmind changed the title 第18题:React 中 setState 什么时候是同步的,什么时候是异步的? 第 18 题:React 中 setState 什么时候是同步的,什么时候是异步的? Apr 26, 2019
@adminparry
Copy link

我的个人观点是这样的对于同步或者是异步都可以进行控制的想要同步就同步想要异步就异步
我老婆提醒我如果面试官问什么你管不了 谁让你想去那工作呢

@ggzcg
Copy link

ggzcg commented Jul 12, 2019

@yygmind 被这问题吓到了,还好点进来看了,你做出了我放心的解释

@CC712
Copy link

CC712 commented Aug 2, 2019

React Dan Abramov 大佬的解释
这题应该是想问 setStatereconiliation 特性

@dongkeng001
Copy link

由React控制的事件处理程序,以及生命周期函数调用setState不会同步更新state 。
React控制之外的事件中调用setState是同步更新的。比如原生js绑定的事件,setTimeout/setInterval等。

@yygmind yygmind added the Vue label Dec 16, 2019
@yygmind yygmind added React and removed Vue labels Jan 2, 2020
@tenadolanter
Copy link

单独的说同步异步没有意义,一般配合场景:setState后取值使用的,初学者经常发现setState后值还是旧的,而且这个错误很难发现。

@qiruohan
Copy link

异步更新,同步执行

@stevekeol
Copy link

stevekeol commented Apr 7, 2020

React的setState本身并不是异步的,是因为其批处理机制给人一种异步的假象。

【React的更新机制】

生命周期函数和合成事件中:

  1. 无论调用多少次setState,都不会立即执行更新。而是将要更新的state存入'_pendingStateQuene',将要更新的组件存入'dirtyComponent';
  2. 当根组件didMount后,批处理机制更新为false。此时再取出'_pendingStateQuene'和'dirtyComponent'中的state和组件进行合并更新;

原生事件和异步代码中:

  1. 原生事件不会触发react的批处理机制,因而调用setState会直接更新;
  2. 异步代码中调用setState,由于js的异步处理机制,异步代码会暂存,等待同步代码执行完毕再执行,此时react的批处理机制已经结束,因而直接更新。

总结:
react会表现出同步和异步的现象,但本质上是同步的,是其批处理机制造成了一种异步的假象。(其实完全可以在开发过程中,在合成事件和生命周期函数里,完全可以将其视为异步)

@taichiyi
Copy link

taichiyi commented Aug 3, 2020

先说结论:

如果是通过 DOM 事件(例如:onClick) 的事件处理函数调用了 setState 函数,导致 React 进行更新(或者说“调度”scheduleWork),则此时事件处理函数中的 setState 的行为是异步的(也就是合并更新)。

上面的评论中说了很多 setState “异步”的情况。我这里补充一下“同步”的情况,下面的例子使用了 setTimeOut,组件最后显示结果是33

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
    this.handleAddCount = this.handleAddCount.bind(this);
  }
  componentDidMount() {
    setTimeout(this.handleAddCount, 1*1000);
  }
  handleAddCount() {
    this.setState({
      count: this.state.count + 11
    });
    this.setState({
      count: this.state.count + 22
    });
  }
  render() {
    return <div>{this.state.count}</div>;
  }
}

源码:

从源码的角度来说,setState 的行为是“异步”还是“同步”取决于 React 执行 setState 方法时的执行上下文①(ExecutionContext)。

如果 ExecutionContext 为 0,表示当前没有正在进行的其他任务,则 setState 是“同步”的。React 源码地址:https://github.com/facebook/react/blob/b53ea6ca05d2ccb9950b40b33f74dfee0421d872/packages/react-reconciler/src/ReactFiberWorkLoop.js#L411

①注意这里的执行上下文不是浏览器的,为了更好的控制渲染任务,避免长时间占用浏览器的主线程, React 实现了自己的执行上下文

@7kms
Copy link

7kms commented Oct 15, 2020

setState是同步还是异步?

基于当前最新稳定版本v16.14.0进行分析

此处同步异步的定义

我们知道Promise.then(),setTimeout是异步执行. 从js执行来说, setState肯定是同步执行.

所以这里讨论的同步和异步并不是指setState是否异步执行, 而是指调用setState之后this.state能否立即更新.

分析

  1. 众所周知调用setState({item: 'new xxx'})之后, 会将传入setState的参数包装成一个update对象并添加到updateQueue队列中.
  2. 之后updateQueue队列在什么时机被合并到this.state中才是本题目的关键. 因为合并之后this.state必然就已经更新了.
  3. state的合并是在fiber构建循环中进行的, 而fiber构建循环必然是在触发scheduler调度之后进行. 关于这一点的详细论述可以参考react两大工作循环.

到这里问题转化为调用setState之后, 是否立即触发scheduler调度?

  • 如果立即进行scheduler调度, 那么this.state必然能同步获取.
  • 反之, 如果异步进行scheduler调度, 那么this.state不能同步获取.
  1. 每次调用setState都会进行一次scheduler调度(可以参考React 调度机制).
    在最新源码v16.14.0中体现为调用ensureRootIsScheduled. 在该源码中, 可以得到回答本题目的最佳答案, 核心逻辑如下:
 if (
      (executionContext & LegacyUnbatchedContext) !== NoContext &&
      (executionContext & (RenderContext | CommitContext)) === NoContext
    ) {
       // .... 省略部分本次讨论不会涉及的代码
    } else {
      ensureRootIsScheduled(root, eventTime); // 触发scheduler调度(调度是异步的) , 所以该函数不会立即触发render.
      if (executionContext === NoContext) {  // 当执行上下文为0时, 会刷新同步队列
         // .... 省略部分本次讨论不会涉及的代码

        // 这里是关键,  执行同步回调队列. 有兴趣的同学可以继续在源码中查看, 可以得到结论:
        // if分支之外的ensureRootIsScheduled(root, eventTime)和此处的flushSyncCallbackQueue()
        // 最终都是调用performSyncWorkOnRoot进行fiber树的循环构建
        flushSyncCallbackQueue(); 
      }
    }

结论

  1. 如楼上 @taichiyi 所述, setState是同步和异步最关键的因素是react内部的执行上下文executionContext的状态.
  • executionContext为空时, 表现为同步.
  • 反之executionContext不为空, 表现为异步.
  1. executionContext何时为空?

这个问题反过来更好理解, 什么时候executionContext不为空? 因为executionContext是react内部控制的属性, 当初次render, 合成事件触发时都会改变executionContext的值.

  1. 只要绕开react内部触发更改executionContext的逻辑, 就能保证executionContext为空, 进而实现setState为同步.
  • 可以使用异步调用如setTimeout, Promise, MessageChannel等
  • 可以监听原生事件, 注意不是合成事件(合成事件是react体系, 会更改executionContext), 在原生事件的回调函数中执行 setState 就是同步的

附加条件

以上分析都是基于legacy模式进行分析的, 众所周知react即将(可能)全面进入concurrent模式(可以参考react 启动模式). 在concurrent模式下, 这个题目可能就没有意义了, 因为从目前最新代码来看, 在concurrent模式下根本就不会判断executionContext, 所以concurrent模式下setState都为异步.

 // concurrent模式下根本没有下列代码, 所以不可能同步
if (executionContext === NoContext) { 
        flushSyncCallbackQueue(); 
      }

演示示例

对于此问题在codesandbox上面做了一个demo(详细演示示例). 在legacyconcurrent模式下演示并验证了上述结论.

@yoghurtxu
Copy link

我们知道Promise.then(),setTimeout是异步执行. 从js执行来说, setState肯定是同步执行.

所以这里讨论的同步和异步并不是指setState是否异步执行, 而是指调用setState之后this.state能否立即更新.

@vkboo
Copy link

vkboo commented Jun 17, 2021

setState是异步的,setState之后,取state中的值并不一定是最新更新的值。
这样设计的原因是可能在同步的代码中可能存在连续的多个setSate操作,react会对他们进行智能的合并,直到执行到了最后一个setState,React才回智能的合并state的,并异步的设置state的值,后续判断是否进行render操作.如果同步的每次setState都去走一遍reconciler & commit操作,则太耗性能了

@minhuaF
Copy link

minhuaF commented Jul 29, 2021

react 18 发布之后,估计要对不同版本不同模式进行不同的回答了

@yqz0203
Copy link

yqz0203 commented Mar 15, 2022

为什么setTimeout里面的setState就不能进行批处理?如果批处理会有什么异常,望解答。

@littleee
Copy link

isBatchingUpdates = true

setTimeout(() => {
    this.setState({
        count: this.state.count + 1
    })
}, 0)

isBatchingUpdates = false

看了这段应该就清晰很多了。 react 会在执行前加一个“锁”来标记是否需要批处理, 如上,react加了锁之后立刻就释放了,然后才会执行setTimeout里的setState, 也就是说setTimeout和原生事件会脱离react的控制。 只有在react控制下才会存在批处理,setState才会有“异步”效果。

为什么setTimeout里面的setState就不能进行批处理?如果批处理会有什么异常,望解答。

@Verten
Copy link

Verten commented May 20, 2022

在最新的版本里面,React已经默认auto batch updating了

reactwg/react-18#21

@littleee
Copy link

littleee commented May 20, 2022

在最新的版本里面,React已经默认auto batch updating了

reactwg/react-18#21

针对这个问题,大家默认讨论版本是react15, fiber之前

@Yuweiai
Copy link

Yuweiai commented Jul 4, 2022

(微医)React 中 setState 什么时候是同步的,什么时候是异步的?

【高能预警:你懂得越多,你不懂得更多】

  1. 4. 基础篇-玄学state:在不同的执行环境下,或者不同的 React 模式下,State 更新流程都是不同的

  2. React 是有多种模式的,基本平时用的都是 legacy 模式

    • 除了 legacy 模式,还有 blocking 模式和 concurrent 模式 —— blocking 可以视为 concurrent 的优雅降级版本和过渡版本,React 最终目的,不久的将来将以 concurrent 模式作为默认版本,这个模式下会开启一些新功能
    • React V18 版本,concurrent 将作为一个稳定的功能出现

1. legacy 模式下的 setState

1.1 React 在触发 setState 时主要做了哪些事:

  • 首先,setState 会产生当前更新的优先级(老版本用 expirationTime,新版本用 lane)
  • 接下来 React 会从 fiber Root 根部 fiber 向下调和子节点,调和阶段将对比发生更新的地方,更新对比 expirationTime,找到发生更新的组件,合并 state,然后触发 render 函数,得到新的 UI 视图层,完成 render
  • 接下来来到 commit 阶段,替换真实 DOM,完成此次更新流程,最后会执行 setState 中 callback 函数,到此为止完成了一次 setState 全过程

一个主要任务的先后顺序:render 阶段 render 函数执行 -> commit 阶段真实 DOM 替换 -> setState 回调函数执行 callback

1.2 类组件 setState 原理揭秘:

  • 类组件初始化过程中绑定了负责更新的 Updater 对象,调用 setState,实际上是 React 底层调用 Updater 对象上的 enqueueSetState 方法

    // react-reconciler/src/ReactFiberClassComponent.js
    
    enqueueSetState(){
         /* 每一次调用`setState`,react 都会创建一个 update 里面保存了 */
         const update = createUpdate(expirationTime, suspenseConfig);
         /* callback 可以理解为 setState 回调函数,第二个参数 */
         callback && (update.callback = callback) 
         /* enqueueUpdate 把当前的update 传入当前fiber,待更新队列中 */
         enqueueUpdate(fiber, update); 
         /* 开始调度更新 */
         scheduleUpdateOnFiber(fiber, expirationTime);
    }

    enqueueSetState 会创建一个 update,然后放入当前 fiber 对象的待更新队列中,最后开启调度更新,进入到(批量)更新流程

  • React 批量更新:

    // react-dom/src/events/DOMLegacyEventPluginSystem.js
    
    /* 在`legacy`模式下,所有的事件都将经过此函数同一处理 */
    function dispatchEventForLegacyPluginEventSystem(){
        // handleTopLevel 事件处理函数
        batchedEventUpdates(handleTopLevel, bookKeeping);
    }
    // react-dom/src/events/ReactDOMUpdateBatching.js
    
    function batchedEventUpdates(fn,a){
      /* 开启批量更新  */
      isBatchingEventUpdates = true;
      try {
        /* 这里执行了的事件处理函数, 比如在一次点击事件中触发setState,那么它将在这个函数内执行 */
        return batchedEventUpdatesImpl(fn, a, b);
      } finally {
        /* try 里面 return 不会影响 finally 执行  */
        /* 完成一次事件,批量更新  */
        isBatchingEventUpdates = false;
      }
    }
    • 在 React 事件执行之前通过 isBatchingEventUpdates = true打开开关,开启事件批量更新,当该事件结束,再通过isBatchingEventUpdates = false关闭开关,然后在·scheduleUpdateOnFiber `中根据这个开关来确定是否进行批量更新
    export default class index extends React.Component{
        state = { number:0 }
        handleClick= () => {
              this.setState({ number:this.state.number + 1 },()=>{   console.log( 'callback1', this.state.number)  })
              console.log(this.state.number)
              this.setState({ number:this.state.number + 1 },()=>{   console.log( 'callback2', this.state.number)  })
              console.log(this.state.number)
              this.setState({ number:this.state.number + 1 },()=>{   console.log( 'callback3', this.state.number)  })
              console.log(this.state.number)
        }
        render(){
            return <div>
                { this.state.number }
                <button onClick={ this.handleClick }  >number++</button>
            </div>
        }
    }

    0, 0, 0, callback1 1 ,callback2 1 ,callback3 1

  • 异步操作(promise 或者 setTimeout)打破批量更新规则:

    • setTimeout 中的 setState 为什么会多次 render?
      • 因为异步出了事件系统的执行上下文:触发时 isBatchingUpdates 为 false,所以多次 setState 没有被合并处理,能够直接进行更新
    setTimeout(()=>{
        this.setState({ number:this.state.number + 1 },()=>{   console.log( 'callback1', this.state.number)  })
        console.log(this.state.number)
        this.setState({ number:this.state.number + 1 },()=>{    console.log( 'callback2', this.state.number)  })
        console.log(this.state.number)
        this.setState({ number:this.state.number + 1 },()=>{   console.log( 'callback3', this.state.number)  })
        console.log(this.state.number)
    })

    callback1 1 , 1, callback2 2 , 2,callback3 3 , 3

  • 异步环境继续开启批量更新模式:

    React-Dom 中提供了批量更新方法 unstable_batchedUpdates,可以去手动批量更新

    import ReactDOM from 'react-dom'
    const { unstable_batchedUpdates } = ReactDOM
    
    setTimeout(()=>{
        unstable_batchedUpdates(()=>{
            this.setState({ number:this.state.number + 1 })
            console.log(this.state.number)
            this.setState({ number:this.state.number + 1})
            console.log(this.state.number)
            this.setState({ number:this.state.number + 1 })
            console.log(this.state.number) 
        })
    })

    0 , 0 , 0 , callback1 1 , callback2 1 ,callback3 1

    在实际工作中,unstable_batchedUpdates可以用于 Ajax 数据交互之后,合并多次 setState,或者时多次 useState —— 如果批量更新处理,一次数据交互多次改变 state 会促使视图多次渲染

  • 提升更新优先级:

    React-dom 提供了 flushSync ,flushSync 可以将回调函数中的更新任务,放在一个较高的优先级中

    handerClick=()=>{
        setTimeout(()=>{
            this.setState({ number: 1  })
        })
        
        this.setState({ number: 2  })
        
        ReactDOM.flushSync(()=>{
            this.setState({ number: 3  })
        })
        
        this.setState({ number: 4  })
    }
    
    render(){
       console.log(this.state.number)
       return ...
    }

    3 4 1

    首先 flushSync this.setState({ number: 3 })设定了一个高优先级的更新,然后 2 和 3 被批量更新到 3,最后更新 setTimeout 中的 1

    flushSync 中的 setState > 正常执行上下文中的 setState > setTimeout、Promise 中的 setState

1.3 函数组件 useState 原理揭秘

(未完待下一道面试题)18. 原理篇-hooks原理

2. V18concurrent模式下的 setState

(未完待下一道面试题)34. v18特性篇-concurrent 下的 state更新流程

@yqz0203
Copy link

yqz0203 commented Jul 8, 2022

isBatchingUpdates = true

setTimeout(() => {
    this.setState({
        count: this.state.count + 1
    })
}, 0)

isBatchingUpdates = false

看了这段应该就清晰很多了。 react 会在执行前加一个“锁”来标记是否需要批处理, 如上,react加了锁之后立刻就释放了,然后才会执行setTimeout里的setState, 也就是说setTimeout和原生事件会脱离react的控制。 只有在react控制下才会存在批处理,setState才会有“异步”效果。

为什么setTimeout里面的setState就不能进行批处理?如果批处理会有什么异常,望解答。

这里的 “控制” 是指什么呢?如果脱离控制后会发生什么呢?

@janyin
Copy link

janyin commented Oct 11, 2022

setTimeout/setInterval调用并不是同步setState,也是异步的

在线例子:https://stackblitz.com/edit/react-ts-sqs8mw?file=App.tsx

@janyin
Copy link

janyin commented Oct 11, 2022

image

这段代码里输出的a是0,而不是3. 证明在setimeout里setState也是异步的

@Akarinx
Copy link

Akarinx commented Oct 11, 2022

@janyin clickHandler 是通过 react 合成事件调用的呀

@janyin
Copy link

janyin commented Oct 11, 2022

Akarinx

是合成事件,但是里面用的是setTimeout

@janyin
Copy link

janyin commented Oct 11, 2022

这个issue之前是没问题的。React18后setState全部都是异步的,所以不需要再分情况讨论,包括原生DOM事件和setTimeout之类的。

另外我觉得讨论这个问题没多大意义,因为setState本身设计出来就是异步的,要同步的访问state就用第二个参数回调访问啊。不然别人设计出第二个参数干嘛。

  • 面试应该问:为什么setState是异步的?
  • 而不是问:什么情况是同步,什么情况是异步?

参考: facebook/react#11527

@janyin
Copy link

janyin commented Oct 11, 2022

这个是原生事件的例子: https://stackblitz.com/edit/react-ts-kabj1e?file=index.tsx,App.tsx
image

@janyin clickHandler 是通过 react 合成事件调用的呀

@Akarinx
Copy link

Akarinx commented Oct 11, 2022

这个是原生事件的例子: https://stackblitz.com/edit/react-ts-kabj1e?file=index.tsx,App.tsx image

@janyin clickHandler 是通过 react 合成事件调用的呀

噢噢 是我这边的问题,确实是因为 react18 的问题

@Frank-MJ
Copy link

Frank-MJ commented Apr 9, 2023

如果setState 在React 能够控制的范围被调用,它就是异步的。 例如:合成事件处理函数, 生命周期函数, 此时会进行批量更新, 也就是将状态合并后再进行DOM 更新。 如果setState 在原生JavaScript 控制的范围被调用,它就是同步的

@MiJiaCoding
Copy link

(微医)React 中 setState 什么时候是同步的,什么时候是异步的?

【高能预警:你懂得越多,你不懂得更多】

  1. 4. 基础篇-玄学state:在不同的执行环境下,或者不同的 React 模式下,State 更新流程都是不同的
  2. React 是有多种模式的,基本平时用的都是 legacy 模式
    • 除了 legacy 模式,还有 blocking 模式和 concurrent 模式 —— blocking 可以视为 concurrent 的优雅降级版本和过渡版本,React 最终目的,不久的将来将以 concurrent 模式作为默认版本,这个模式下会开启一些新功能
    • React V18 版本,concurrent 将作为一个稳定的功能出现

1. legacy 模式下的 setState

1.1 React 在触发 setState 时主要做了哪些事:

  • 首先,setState 会产生当前更新的优先级(老版本用 expirationTime,新版本用 lane)
  • 接下来 React 会从 fiber Root 根部 fiber 向下调和子节点,调和阶段将对比发生更新的地方,更新对比 expirationTime,找到发生更新的组件,合并 state,然后触发 render 函数,得到新的 UI 视图层,完成 render
  • 接下来来到 commit 阶段,替换真实 DOM,完成此次更新流程,最后会执行 setState 中 callback 函数,到此为止完成了一次 setState 全过程

一个主要任务的先后顺序:render 阶段 render 函数执行 -> commit 阶段真实 DOM 替换 -> setState 回调函数执行 callback

1.2 类组件 setState 原理揭秘:

  • 类组件初始化过程中绑定了负责更新的 Updater 对象,调用 setState,实际上是 React 底层调用 Updater 对象上的 enqueueSetState 方法

    // react-reconciler/src/ReactFiberClassComponent.js
    
    enqueueSetState(){
         /* 每一次调用`setState`,react 都会创建一个 update 里面保存了 */
         const update = createUpdate(expirationTime, suspenseConfig);
         /* callback 可以理解为 setState 回调函数,第二个参数 */
         callback && (update.callback = callback) 
         /* enqueueUpdate 把当前的update 传入当前fiber,待更新队列中 */
         enqueueUpdate(fiber, update); 
         /* 开始调度更新 */
         scheduleUpdateOnFiber(fiber, expirationTime);
    }

    enqueueSetState 会创建一个 update,然后放入当前 fiber 对象的待更新队列中,最后开启调度更新,进入到(批量)更新流程

  • React 批量更新:

    // react-dom/src/events/DOMLegacyEventPluginSystem.js
    
    /* 在`legacy`模式下,所有的事件都将经过此函数同一处理 */
    function dispatchEventForLegacyPluginEventSystem(){
        // handleTopLevel 事件处理函数
        batchedEventUpdates(handleTopLevel, bookKeeping);
    }
    // react-dom/src/events/ReactDOMUpdateBatching.js
    
    function batchedEventUpdates(fn,a){
      /* 开启批量更新  */
      isBatchingEventUpdates = true;
      try {
        /* 这里执行了的事件处理函数, 比如在一次点击事件中触发setState,那么它将在这个函数内执行 */
        return batchedEventUpdatesImpl(fn, a, b);
      } finally {
        /* try 里面 return 不会影响 finally 执行  */
        /* 完成一次事件,批量更新  */
        isBatchingEventUpdates = false;
      }
    }
    • 在 React 事件执行之前通过 isBatchingEventUpdates = true打开开关,开启事件批量更新,当该事件结束,再通过isBatchingEventUpdates = false关闭开关,然后在·scheduleUpdateOnFiber `中根据这个开关来确定是否进行批量更新
    export default class index extends React.Component{
        state = { number:0 }
        handleClick= () => {
              this.setState({ number:this.state.number + 1 },()=>{   console.log( 'callback1', this.state.number)  })
              console.log(this.state.number)
              this.setState({ number:this.state.number + 1 },()=>{   console.log( 'callback2', this.state.number)  })
              console.log(this.state.number)
              this.setState({ number:this.state.number + 1 },()=>{   console.log( 'callback3', this.state.number)  })
              console.log(this.state.number)
        }
        render(){
            return <div>
                { this.state.number }
                <button onClick={ this.handleClick }  >number++</button>
            </div>
        }
    }

    0, 0, 0, callback1 1 ,callback2 1 ,callback3 1

  • 异步操作(promise 或者 setTimeout)打破批量更新规则:

    • setTimeout 中的 setState 为什么会多次 render?

      • 因为异步出了事件系统的执行上下文:触发时 isBatchingUpdates 为 false,所以多次 setState 没有被合并处理,能够直接进行更新
    setTimeout(()=>{
        this.setState({ number:this.state.number + 1 },()=>{   console.log( 'callback1', this.state.number)  })
        console.log(this.state.number)
        this.setState({ number:this.state.number + 1 },()=>{    console.log( 'callback2', this.state.number)  })
        console.log(this.state.number)
        this.setState({ number:this.state.number + 1 },()=>{   console.log( 'callback3', this.state.number)  })
        console.log(this.state.number)
    })

    callback1 1 , 1, callback2 2 , 2,callback3 3 , 3

  • 异步环境继续开启批量更新模式:
    React-Dom 中提供了批量更新方法 unstable_batchedUpdates,可以去手动批量更新

    import ReactDOM from 'react-dom'
    const { unstable_batchedUpdates } = ReactDOM
    
    setTimeout(()=>{
        unstable_batchedUpdates(()=>{
            this.setState({ number:this.state.number + 1 })
            console.log(this.state.number)
            this.setState({ number:this.state.number + 1})
            console.log(this.state.number)
            this.setState({ number:this.state.number + 1 })
            console.log(this.state.number) 
        })
    })

    0 , 0 , 0 , callback1 1 , callback2 1 ,callback3 1

    在实际工作中,unstable_batchedUpdates可以用于 Ajax 数据交互之后,合并多次 setState,或者时多次 useState —— 如果批量更新处理,一次数据交互多次改变 state 会促使视图多次渲染

  • 提升更新优先级:
    React-dom 提供了 flushSync ,flushSync 可以将回调函数中的更新任务,放在一个较高的优先级中

    handerClick=()=>{
        setTimeout(()=>{
            this.setState({ number: 1  })
        })
        
        this.setState({ number: 2  })
        
        ReactDOM.flushSync(()=>{
            this.setState({ number: 3  })
        })
        
        this.setState({ number: 4  })
    }
    
    render(){
       console.log(this.state.number)
       return ...
    }

    3 4 1

    首先 flushSync this.setState({ number: 3 })设定了一个高优先级的更新,然后 2 和 3 被批量更新到 3,最后更新 setTimeout 中的 1

    flushSync 中的 setState > 正常执行上下文中的 setState > setTimeout、Promise 中的 setState

1.3 函数组件 useState 原理揭秘

(未完待下一道面试题)18. 原理篇-hooks原理

2. V18concurrent模式下的 setState

(未完待下一道面试题)34. v18特性篇-concurrent 下的 state更新流程
原文:"首先 flushSync this.setState({ number: 3 })设定了一个高优先级的更新,然后 2 和 3 被批量更新到 3,最后更新 setTimeout 中的 1"
2,3批量更新到3是不是打错了? 是2,4批量更新到4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests