跳到主要内容

setState

一、认识


this.setState() 更新 React Class 组件的 state

二、语法


this.setState(nextState, callback?) 
  • nextState: 一个对象或者函数

    • 对象: 传递一个对象作为 nextState,它将浅层合并到 this.state

    • 函数: 是一个纯函数, 函数会将先前的 prevStateprevProps 作为参数, 并且应该返回要浅层合并到 this.state 中的对象。React 会将你的更新函数放入队列中并重新渲染你的组件。在下一次渲染期间,React 将通过应用队列中的所有的更新函数来计算下一个 state

  • callback: 如果你指定该函数,React 将在提交更新后调用你提供的 callback

2.1 对象

this.setState({ xx: yy }, ()=>{
console.log("this.state", this.state)
});

2.2 函数

this.setState((prevState,prevProps)=> { return { ...prevState }}, ()=> { console.log("this.state", this.state) });

三、更新


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

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

setState 批量更新策略:

  1. 对同 key 多次进行 setState: 如果传入对象, 会其进行覆盖, 取最后一次执行。如果传入函数, 更新函数放入队列, 依次执行, 并计算。

  2. 对不同 key 进行多次进行 setState: 合并批量更新

3.1 React.js 18 之前对象更新

3.2 React.js 18 之后对象更新

import React from 'react';

class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 0
};
}

componentDidMount() {
this.setState({ value: this.state.value + 1 });
console.log(this.state.value); // 0
this.setState({ value: this.state.value + 1 });
console.log(this.state.value); // 0

setTimeout(() => {
this.setState({ value: this.state.value + 2 });
console.log(this.state.value); // 1
this.setState({ value: this.state.value + 1 });
console.log(this.state.value); // 1
}, 0);
}

render() {
return <div>{this.state.value}</div>; // 2
}
}

export default App;

3.3 React.js 18 之前函数更新

3.4 React.js 18 之后函数更新

import React from 'react';

class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 0
};
}

componentDidMount() {
this.setState(prevState => ({ ...prevState, value: prevState.value + 1 }));
console.log(this.state.value); // 0
this.setState(prevState => ({ ...prevState, value: prevState.value + 1 }));
console.log(this.state.value); // 0

setTimeout(() => {
this.setState(prevState => ({
...prevState,
value: prevState.value + 2
}));
console.log(this.state.value); // 2
this.setState(prevState => ({
...prevState,
value: prevState.value + 1
}));
console.log(this.state.value); // 2
}, 0);
}

render() {
return <div>{this.state.value}</div>; // 5
}
}

export default App;

3.5 React.js 18 之前同步更新

3.6 React.js 18 之后同步更新

可以通过 flushSync 进行同步更新, 多次渲染。flushSync 函数内部的多个 setState 仍然为批量更新,这样可以精准控制哪些不需要的批量更新。因此, 想要同步更新, 多次渲染, 需要将多个 setState 分开包裹。

import React from 'react';
import { flushSync } from 'react-dom';

class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 0
};
}

componentDidMount() {
flushSync(() => this.setState({ value: this.state.value + 1 }));
console.log(this.state.value); // 0
flushSync(() => this.setState({ value: this.state.value + 1 }));
console.log(this.state.value); // 0
}

render() {
console.log('更新'); // 执行两次
return <div>{this.state.value}</div>; // 1
}
}

export default App;

四、沉淀与总结


4.1 说说 React 中的 setState 执行机制

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 开启调度更新流程。

4.2 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 进行调度更新, 对比上次等待的更新和本次更新的优先级, 如果相等, 则会终止这个这任务的调度流程, 复用已有的调度任务。