跳到主要内容

认识

2023年06月10日
柏拉文
越努力,越幸运

一、认识


setState 用于 React Class 组件 更新自身状态, 触发更新, 重新渲染。接收两个参数, 第一个参数为需要更新的数据, 可以为对象, 也可以为函数, 对象会浅层合并到 this.state 中, 函数的话会基于之前的 stateprops 返回一个新的对象, 最后浅层合并到 this.state 中。setState 可同步, 可异步。所谓异步就是批量处理。

React.js 18.x 之前, React合成事件生命周期钩子(除 componentDidUpdate将会批量处理更新。但是在 Promise.thensetTimeout 或者原生事件处理程序中, 更新将会以同步的方式处理。

React.js 18.x 之后, 几乎所有更新, 包括 Promise.thensetTimeout 或者原生事件处理程序等, 都将自动批处理。因此, 在 React.js 18.x 之后, 如果需要强制同步更新 setState, 可以将更新包装在 flushSync 中, 但是这样会损害性能。

批量更新中, 如果对同一个值进行多次 setStatesetState的批量更新策略会对其进行覆盖,取最后一次的执行; 如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。

setState 执行机制为: 创建一个对应优先级的 update, 并加入更新队列, 调用 scheduleUpdateOnFiber 开启调度更新流程。

二、细节


三、问题


3.1 setState 是同步的还是异步的?什么时候是同步的?什么时候是异步的?

setState 可同步, 可异步。所谓异步就是批量处理。

React.js 18.x 之前, React合成事件生命周期钩子(除 componentDidUpdate将会批量处理更新。但是在 Promise.thensetTimeout 或者原生事件处理程序中, 更新将会以同步的方式处理。

React.js 18.x 之后, 几乎所有更新, 包括 Promise.thensetTimeout 或者原生事件处理程序等, 都将自动批处理。因此, 在 React.js 18.x 之后, 如果需要强制同步更新 setState, 可以将更新包装在 flushSync 中, 但是这样会损害性能。

批量更新中, 如果对同一个值进行多次 setStatesetState的批量更新策略会对其进行覆盖,取最后一次的执行; 如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。

React.js 18.x 之前的批量处理原理: 通过全局变量 executionContext 控制 React 执行上下文,指示 React 开启同步或者异步更新。executionContext 一开始被初始化为 NoContext,因此 React 默认是同步更新的。在合成事件生命周期钩子(除 componentDidUpdate 除外), 中, 一开始这个变量会赋值为一个 BatchedContext, 在此期间, 如果多次同步调用更新函数, 会走批量更新逻辑, 构造更新队列, 将多个更新函数加入到更新队列, 等到当前环境执行完毕后, 将 executionContext 恢复为之前状态, 根据更新队列进行调度更新。如果异步调用更新函数, 此时 executionContext 已经恢复之前状态, 直接开始更新任务。

React.js 18.x 之后的批量处理原理: 在同一环境中, 无论同步调用还是异步调用, 多次更新函数任务的优先级是相同的, 属于同一批任务。这一批任务都通过 scheduleUpdateOnFiber 标记相同的优先级之后, 开始通过 ensureRootIsScheduled 进行调度更新, 对比上次等待的更新和本次更新的优先级, 如果相等, 则会终止这个这任务的调度流程, 复用已有的调度任务。

3.2 ReactsetState 中什么时候会合并更新?怎么实现链式更新?

  • setState使用对象语法时更新会被合并,合并的结果就是只会执行最后一次的setState操作。

    import React from 'react';

    class App extends React.Component {
    constructor(props) {
    super(props);
    this.state = {
    count: 0
    };
    }
    componentDidMount() {
    this.handleAdd(1); // 不会执行
    this.handleAdd(2); // 最后一次的 setState 才会被执行
    }
    handleAdd(value) {
    this.setState(
    {
    count: this.state.count + value
    },
    () => {
    console.log(this.state.count); // 结果是 0+2 =2
    }
    );
    }
    render() {
    return (
    <div>
    <h3>{this.state.count}</h3>
    </div>
    );
    }
    }

    export default App;
  • setState使用函数语法时可以实现链式更新。链式更新的结果就是每一次的setState都会被执行

    import React from 'react';

    class App extends React.Component {
    constructor(props) {
    super(props);
    this.state = {
    count: 0
    };
    }
    componentDidMount() {
    this.handleAdd(1); // 被执行
    this.handleAdd(2); // 被执行
    }
    handleAdd(value) {
    this.setState(
    state => {
    return {
    count: state.count + value
    };
    },
    () => {
    console.log(this.state.count); // 结果是 0+1 = 1 1+2 = 3
    }
    );
    }
    render() {
    return (
    <div>
    <h3>{this.state.count}</h3>
    </div>
    );
    }
    }

    export default App;