【React】setState的批处理

本文作者: 小炒芝士

setState的特点

setState(updater, [callback]) // class写法
const [value, setValue] = useState(init) // hook写法
复制代码
  1. setState/setValue将最新的state排入队列,并通知React有更新,需要重新渲染父组件和子组件
  2. 以上两种方式导致的state变更,并不会及时(同步)更新组件,而是批量、异步更新组件,所以,在调用setState(setValue同)后,有很大可能无法获取到最新的状态值
  3. 针对2中问题,如果使用setState,可以在callback或者componentDidUpdate中执行state变更后的处理,如果使用useState,则可以在useEffect中订阅状态的变更,做相应处理

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

为什么setState 会批处理,什么时候会批处理?

为什么要批处理

我们考虑一下,以React的使用者的角度来看,如果每一次state更新都会导致组件的重新渲染的话,对性能有很大的影响。所以批量处理state的更新是必然的。

从React的开发者的角度,如果每一次都更新

  • 这样会破坏掉 props 和 state 之间的一致性,造成一些难以 debug 的问题。
  • 这样会让一些我们正在实现的新功能变得无法实现。

何时会批处理

在React16版本及以前,React 会对所有React内部触发的事件监听函数中的更新(比如onClick函数)做批处理,如果是绕过react组件,如addEventListenr,或者异步调用如异步请求或者setTimeout等,不会进行批处理

在React17版本及之后,React会对所有的更新做批处理

如果我们想要对非react内部触发函数进行批处理,如下

Promise().then(() => {
  ReactDOM.unstable_batchedUpdates(() => {
  this.setState({ k: 1 })
  this.setState({ x: 2 })
  })
}) 
复制代码

原理:
在React的setState函数中,会根据一个变量isBatchingUpdates来判断当前变更是直接更新state还是将其加入队列中,isBatchingUpdates的默认值是false,但是在React调用事件处理函数的时候,会先调用bachedUpdates,将isBatchingUpdates的值置为true,那么此时,所有的更新都会被放进队列之中,直到函数结束,此时更新state

image.png

如何获取到最新的state值?

批处理导致无法获取最新的state,如下

import React, { Component } from "react";
class App extends Component { 
  state = { title: 0 };

  componentDidMount() {}

  handleClick = () => {
     // 批处理
     this.setState({ title: this.state.title + 1 });
     this.setState({ title: this.state.title + 1 })
    console.log(this.state.title) // 0
  };

  render() {
    const { title } = this.state;
    console.log('render', this.state.title) // 'render', 1
    return ( <div id="id" onClick={this.handleClick}> {title} </div> );
  }
} 

export default App; 

复制代码

1、方案1,给setState传递一个函数,访问当前state

handleClick = () => { 
  // setState第一个参数为函数时,函数参数分别为prevState,prevProps,可以获取到最新state 
  this.setState((prevState) => ({ title: prevState.title + 1 })); 
  this.setState((prevState) => ({ title: prevState.title + 1 })); 
}

render () {
  console.log('render', this.state.title) // 2
  return <div id="id" onClick={this.handleClick}>{title}</div>
} 
复制代码

2、方案2,使用setState的第二个参数,回调函数, 获取最新state

// 回调函数可以获取最新的state
 handleClick = () => {
    this.setState({ title: this.state.title + 1 }, () => {
      console.log(this.state.title) // 1
    })
    console.log(this.state.title) // 0 
} 

render() {
  console.log('render', this.state.title) // 1
  return ( <div id="id" onClick={this.handleClick}> {title} </div> ); 
} 
复制代码

注:回调函数是在setState完成,并且render 执行之后调用的

handleClick = () => {
  this.setState( (prevState) => ({ title: prevState.title + 1 }), () => {
    console.log("1", this.state); // "1",2 
  } );
  this.setState( (prevState) => ({ title: prevState.title + 1 }), () => { 
    console.log("2", this.state); // "2", 2 
  } );
} 
  render() {
    console.log('render', this.state.title) // 2
    return ( <div id="id" onClick={this.handleClick}> {title} </div> ); 
} 
复制代码

以上包含一些个人理解,如果不对请指出,谢谢!

参考文档:

github.com/sisterAn/bl…

react官方文档

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享