React--setState深入理解

为何使用setState

  • React 中不能直接修改 state 的值来让界面发生更新,因为 React 并没有实现类似 Vue2 中的 Object.defineProperty,或是 Vue3 中的 Proxy 的方式进行数据劫持,监听数据变化
  • React 中必须通过调用 setState 来告知 React 数据已经发生了变化
  • setState 方法是从 Component 中继承过来的

setState后发生了什么?

  • 调用 setState 函数之后,React 会将传入的参数对象与组件当前状态合并,然后触发调和过程,经过调和过程,React 会以相对高效的方式,根据新的状态构建 React 元素树并重新渲染整个 UI 界面
  • React 得到元素树之后, React 会自动计算出新旧树节点的差异,根据差异对界面进行最小化重新渲染,React 的差异算法能相对精确得知发生改变的节点,保证按需更新

setState更多用法

①传入对象 ,告知 React 需要更新 state 中的哪些数据

  • 这里传入一个对象,React 底层实际上是将该对象和 this.state 做一个合并的操作 ,然后在合适的时机,重新调用了 render 函数
  • 通过 Object.assign(传入对象, this.state) 合并,更新 state 中的数据
javascript 复制代码
 this.setState({
   text: '你好啊,React'
 })

②传入回调函数,该回调函数返回一个需要修改的数据对象

  • 可以在该回调函数中编写如何修改 state 中数据的逻辑,耦合性更强
  • 当前的回调函数会将更新前的 stateprops 传递进来,不需要通过 this 获取,当然也可以通过 this.state 访问数据
javascript 复制代码
 this.setState((state, props)=>{
   return { text: '你好啊,React' }
 })

③传入第二个参数setState 中第二个参数是回调函数

  • React 的事件处理中,setState 是异步调用的,如果想要在修改数据后立马拿到最新的数据实现其他的操作,可以在该回调函数中处理
  • 该回调函数是在数据已经完成更新后触发,可拿到最新的数据
javascript 复制代码
 this.setState({
   text: '你好啊,React'
 }, () => {
   console.log('+++++', this.state.text); // +++++ 你好啊,React
 })
 ​
 console.log('-----', this.state.text); // ----- Hello,React

setState异步更新

setState 设计为异步可以显著的提升性能

  • 如果每次调用 setState 都进行一次更新,那么意味着 render 函数会被频繁调用,界面重新渲染,这样会导致效率很低
  • 最好是将每次的 setState 放入到一个任务队列中(微任务),React 事件逻辑处理(宏任务)完成后,再把队列中的 setState 按顺序处理
  • 这样一次性获取到多个 setState 调用,然后再进行批量更新,就不会频繁触发 render
javascript 复制代码
 // 假设setState是同步
 handleCountChange() {
   // 网络请求1完成后...
   this.setState({
     ...
   })
   // 调用render()
   
   // 网络请求2完成后...
   this.setState({
     ...
   })
   // 调用render()
   
   // 网络请求3完成后...
   this.setState({
     ...
   })
   // 调用render()
 }

如果同步更新 state,但还没有执行 render 函数,那么 stateprops 不能保持同步

  • 假设 setState 是同步更新 ,那么在调用 setState 后,this.state 中的数据会立即更新,此时在 render函数执行之前,render 函数中组件传递的 props 依然是更新前的数据
  • 这就会导致 stateprops 不能保持一致性,数据出现混乱,开发中可能会产生许多问题
javascript 复制代码
 import { Component } from 'react'
 ​
 class App extends Component {
   constructor() {
     super()
     this.state = {
       text: 'Hello,React',
     }
   }
 ​
   handleTextChange() {
     // 假设setState同步更新
     this.setState({
       text: '你好啊,React'
     })
     
     // 在这里之后,state中的数据会立即更新
     // this.state = {
     //   text: '你好啊,React'
     // }
     
     // 假如此时并未render函数并未重新执行
   }
 ​
   render() {
     const { text } = this.state // 这里的text依然是'Hello,React'
     return (
       <div>
         <button onClick={e => this.handleTextChange()}>修改文本</button>
         {/* // 这里的text依然是'Hello,React' */}
         <HelloWorld text={text}/>
       </div>
     )
   }
 }

setState同步做法

React18 之前, setState 在某些情况下是同步的

  • setTimeout 中调用 setState
javascript 复制代码
 setTimeout(() => {
   this.setState({
     text: '你好啊,React'
   })
   console.log(this.state.text);
 }, 0);
  • 原生DOM事件回调中调用 setState
javascript 复制代码
 const btnEl = document.querySelector('btn')
 ​
 btnEl.addEventListener('click', () => {
   this.setState({
     text: '你好啊,React'
   })
   console.log(this.state.text);
 })

React18 之前,setState 可以分为同步和异步

  • 在组件生命周期或 React 合成事件中,setState 是异步
  • setTimeout、原生 DOM 事件、Promise 回调中,setState 是同步

React18 之后,默认所有的 setState 都是异步处理,实现批量更新

  • React18 之后如果想实现同步的效果,即调 setState 后立马拿到最新数据,可以手动做一次批量更新
  • react-dom 提供了一个名为 flushSyncAPI,用于实现这种同步效果
javascript 复制代码
 import { flushSync } from 'react-dom'
 ​
 // 这里相当于一次批处理,flushSync回调执行完后立马执行一次render函数
 flushSync(() => {
   this.setState({
     text: '你好啊,React'
   })
 });
 ​
 console.log(this.state.text); // '你好啊,React'
相关推荐
-seventy-5 分钟前
Ajax 与 Vue 框架应用点——随笔谈
前端
我认不到你21 分钟前
antd proFromSelect 懒加载+模糊查询
前端·javascript·react.js·typescript
集成显卡24 分钟前
axios平替!用浏览器自带的fetch处理AJAX(兼容表单/JSON/文件上传)
前端·ajax·json
焚琴煮鹤的熊熊野火33 分钟前
前端垂直居中的多种实现方式及应用分析
前端
我是苏苏1 小时前
C# Main函数中调用异步方法
前端·javascript·c#
转角羊儿1 小时前
uni-app文章列表制作⑧
前端·javascript·uni-app
大G哥1 小时前
python 数据类型----可变数据类型
linux·服务器·开发语言·前端·python
hong_zc2 小时前
初始 html
前端·html
小小吱2 小时前
HTML动画
前端·html
糊涂涂是个小盆友2 小时前
前端 - 使用uniapp+vue搭建前端项目(app端)
前端·vue.js·uni-app