15. setState的更新是异步的吗

setState的更新是异步的?

  • 最终打印结果是Hello World;

  • 可见setState是异步的操作,我们并不能在执行完setState之后里面拿到最新的state的结果

  • 异步执行结果如下图

  • 1.3.3. 为什么setState设计为异步呢?

  • 1.3.4. 我对其回答做一个简单的总结:
    *

    1. setState设计为异步,可以显著的提升性能;
    • 1.1. 如果每次调用setState都进行一次更新,那么以为意味着render函数会被频繁调用,界面重选渲染那,这样效率很低的;

    • 1.2. 最好的办法应该是获取到多个更新,之后进行批量更新;

      1. 如果同步更新了state, 但是还没有执行render函数,那么state和props不能保持同步;
      • 2.1. state和props不能保持一致性,会在开发中产生很多的问题;
      1. setState批量更新机制:
      • 3.1. React18之前是同步的,React18之后都是异步的

      • 3.2. 这里调用了setState三次render函数就会执行三次进行三次diff算法dom会更新三次dom更新的话会引起回流和重绘

      • 3.3. 为什么用调用setState三次?

        • 理想情况下可以直接调用counter + 3
        • 但是在真实开发中会出现componentDidMount里面同时发送网络请求然后差不多时间段返回数据让setState执行多次进行赋值,但是更新state只要更新一次就可以了,更新只要更新一次就可以了
      • 3.4 所以最好的办法:获取到多个更新,之后进行批量更新(一起做一个合并,合并完值之后调用一次render函数就可以,对DOM也是进行一次diff算法,对DOM进行一次更新)

      • 3.5 React18之前有一些地方是不会做批量处理的,例如:promise的then回调不会做批处理,React18之后所有东西都是批量处理

      • 3.6. 关键代码代码如下:

        js 复制代码
          this.setState({
            counter: this.state.counter + 1  // 0 + 1
          })
          this.setState({
            counter: this.state.counter + 1 // 0 + 1
          })
          this.setState({
            counter: this.state.counter + 1 // 0 + 1
          })
      1. setState异步更新的原理:
      • 原理:当执setState更新值操作的时候,会做一个批量处理,把要更新的东西做一个异步操作,先不更新。搞一个队列,会把更新的操作加入到队列里面,等到需要真正更新的时候,取出每次需要更新具体的内容,进行依次合并,队列是有顺序的,队列的特点就是先进先出;
      • 内部源码:内部做了一个do{} while循环,用do{} while循环把队列里面所有东西都给取出来,取出来之后挨个合并合并完之后在执行一次render函数
      1. 上面的代码就会被合并,只会执行一次,真的会被合并吗?
      • 这样来的写的话 this.state.counter + 1,this.state.counter取到的是一个值为0, 所以是: 0 + 1,依然是有合并的,在构建对象的时候,就已经把counter的值取出来了,所以是0+1为1,所以相当于把下面的代码三步做了一样的操作0+1,这里还没涉及设置它
      • 有点类似于obj1的操作 const obj1 = { counter: 0 + 1 }
      • 但是结果是为1的,每次设置值,都是0+1这个值,这个东西同时依然有合并,可以验证它的合并过程,值为1, render函数只执行了一次
      • 如下图:
      • 关键代码如下:
      js 复制代码
       export class App extends Component {
         constructor() {
           super()
           this.state ={
             message: 'hello world',
             counter: 0
           }
         }
      
         increment () {
           console.log('-----------')
           this.setState({
             counter: this.state.counter + 1  // 0 + 1
           })
           this.setState({
             counter: this.state.counter + 1 // 0 + 1
           })
           this.setState({
             counter: this.state.counter + 1 // 0 + 1
           })
         }
      
         render() {
           const { message, counter } = this.state
           console.log('render被执行')
           return (
             <div>
               <h2>message: {message}</h2>
               <button onClick = {e => this.changeText()}>修改文本</button>
               <h2>当前计数:{ counter }</h2>
               <button onClick= { e => this.increment()}>+1</button>
               <Hello message={message} />
             </div>
           )
         }
       }
      1. 其实上面这段代码刚好可以验证是异步的,如果是同步的话,第一个setState更新完之后结果应该是1,第二个setState更新完之后结果应该是2, 第三个setState更新完之后结果应该是3
        但是它没有,结果依旧是1,所以间接证明了它就是异步的,render函数执行一次
      1. 现在合并的值依然是之前的值,如果想要真正的被合并,传入一个回调函数
      • 运行结果如下图:
      • 关键代码如下:
      jsx 复制代码
        // 现在合并的值依然是之前的值,如果想要真正的被合并,传入一个回调函数
        // 这里主要讨论了两个问题:
        // 一:就是在一个点击函数里面执行了三次setState, render函数也只会被执行一次
        // 二:三次里面刚好出现了递增的情况, 需要传入回调函数,在回调函数里面使用传入进来的state, 因为传入进来的state已经是合并完之后另外一个state的值(更新后的值)
        this.setState((state) =>{
          // 使用传递进来state
          // 这个state里面的数据更新 这个本质上是放到一个队列,队列里面放了一个个函数,到时候取出来的时候会回调这个函数,回调这个函数的时候会传入一个state,回调这个函数的时候会把最新的state传进来
          // 经过函数的调用的话就会做一个+1的操作,紧接着取出来第二个函数的时候,又要传入一个state,原来的state就已经+1了,所以传进来的state就是另外一个state(更新计算后的state值), 拿到最新的state进行回调
      
      
          // 这里拿到的this.state的值是不是正确的?
          // 回调这个函数的时候准备做更新了, 不是加入到队列,已经从队列里面取出来了准备开始执行了 ,这个地方this.state的值有没有更新取决于上次更新有没有把上一次this.state里的值有没有改掉还是一次性改掉
          // 取到的值依然是0,那就是依然没有更改,做一次性更改,等到所有东西都合并到一起之后再做一次性更新
          // console.log(this.state.counter)
          return {
            counter: state.counter + 1
          }
        })
        this.setState((state) =>{
          return {
            counter: state.counter + 1
          }
        })
        this.setState((state) =>{
          return {
            counter: state.counter + 1
          }
        })
      1. 3./4./5./6./7.主要讨论了两个问题:
        1. 问题一:就是在一个点击函数里面执行了三次setState, render函数也只会被执行一次
        1. 问题一:调用三次setState里面刚好出现了递增的情况, 需要传入回调函数,在回调函数里面使用传入进来的state, 因为传入进来的state已经是合并完之后另外一个state的值(更新后的值)
1.4. 如何获取异步的结果
    1. 那么如何可以获取更新后的值呢?
    1. 方式一: setState的回调
    • 2.1. setState接受两个参数,第二个参数是一个回调函数,这个回调函数会在更新后执行;
    • 2.2. 格式如下:setState(partialState, callback)
    1. 方式二: 也可以在生命周期函数:
    js 复制代码
      componentDidUpdate(prevProps, prevState, snapshot) {  
        console.log(this.state.message);
      }
1.5. setState一定是异步吗?(React18之前)
    1. 验证一:setTimeout中的更新
    js 复制代码
      setTimeout(() => {
        // 在React18之前,setTimeout中setState操作,是同步操作
        // 在React18之后,setTimeout中setState是异步操作(批处理)
        this.setState({ message: '你好啊,李银河' })
        console.log(this.state.message)  // React18之前`你好啊,李银河`   React18之后`hello World`
      }, 0)
    1. 验证二:原生DOM事件
    js 复制代码
      componentDidMount() {
        const btnEl = document.getElementById('btn');
        btnEl.addEventListener('click', () => {
          this.setState({ message: '你好啊,李银河' }) // React18之前`你好啊,李银河`
        })
      }
      1. 其实分层两种情况:
        1. 在组件生命周期或React合成事件(React事件回调), setState是异步;
        1. 在setTimeou或者原生dom事件中,setState是同步;
1.6. setState一定是异步吗?(React18之后)
    1. 在React18之后,默认所有的操作都被放到了批处理中(异步处理)
    • 官网截图:
    1. 如果希望代码可以同步拿到,则需要执行特殊的flushSync操作;
js 复制代码
 // 导入flushSync
 import { flushSync } from 'react-dom'

// 使用flushSync,传入一个回调函数
 flushSync(() => {
   this.setState({ message: '你好啊,李银河' })
   // 这里的代码依然是批处理,这个回调里面的函数里的代码都是当成一次更新
   console.log('flushSync====',this.state.message) // hello world
 })
 console.log(this.state.message) // `你好啊,李银河`
相关推荐
旧雨散尘8 小时前
【react】react初学6-第一个react应用-待办事项
前端·react.js·前端框架
Eiceblue10 小时前
React 前端实现 Word(Doc/Docx)转 HTML
前端·react.js·word
ideaout技术团队12 小时前
android集成react native组件踩坑笔记(Activity局部展示RN的组件)
android·javascript·笔记·react native·react.js
kaikaile199512 小时前
如何使用React和Redux构建现代化Web应用程序
前端·react.js·前端框架
前端小咸鱼一条12 小时前
13. React中为什么使用setState
前端·javascript·react.js
xw513 小时前
从一个按钮入门CSS in JS之styled-components
css·react.js
微风怎知我意14 小时前
前端框架入门怎么选?一篇搞懂 Vue、React、Angular 的取舍之道
vue.js·react.js·前端框架
Lethehong14 小时前
TRAE SOLO:基于React 18+与蓝耘MaaS的多语言智能翻译平台设计与实现
前端·react.js·前端框架·蓝耘元生代·蓝耘maas
杨筱毅14 小时前
【技术选型】前端框架:Vue vs React - 组合式API与Hooks的哲学之争
vue.js·react.js·前端框架·技术选型