认识
一、认识
setState
用于 React Class
组件 更新自身状态, 触发更新, 重新渲染。接收两个参数, 第一个参数为需要更新的数据, 可以为对象, 也可以为函数, 对象会浅层合并到 this.state
中, 函数的话会基于之前的 state
和 props
返回一个新的对象, 最后浅层合并到 this.state
中。setState
可同步, 可异步。所谓异步就是批量处理。
React.js 18.x
之前, React
在合成事件、生命周期钩子(除 componentDidUpdate
)将会批量处理更新。但是在 Promise.then
、setTimeout
或者原生事件处理程序中, 更新将会以同步的方式处理。
React.js 18.x
之后, 几乎所有更新, 包括 Promise.then
、setTimeout
或者原生事件处理程序等, 都将自动批处理。因此, 在 React.js 18.x
之后, 如果需要强制同步更新 setState
, 可以将更新包装在 flushSync
中, 但是这样会损害性能。
批量更新中, 如果对同一个值进行多次 setState
,setState
的批量更新策略会对其进行覆盖,取最后一次的执行; 如果是同时 setState
多个不同的值,在更新时会对其进行合并批量更新。
setState
执行机制为: 创建一个对应优先级的 update
, 并加入更新队列, 调用 scheduleUpdateOnFiber
开启调度更新流程。
二、细节
三、问题
3.1 setState 是同步的还是异步的?什么时候是同步的?什么时候是异步的?
setState
可同步, 可异步。所谓异步就是批量处理。
React.js 18.x
之前, React
在合成事件、生命周期钩子(除 componentDidUpdate
)将会批量处理更新。但是在 Promise.then
、setTimeout
或者原生事件处理程序中, 更新将会以同步的方式处理。
React.js 18.x
之后, 几乎所有更新, 包括 Promise.then
、setTimeout
或者原生事件处理程序等, 都将自动批处理。因此, 在 React.js 18.x
之后, 如果需要强制同步更新 setState
, 可以将更新包装在 flushSync
中, 但是这样会损害性能。
批量更新中, 如果对同一个值进行多次 setState
,setState
的批量更新策略会对其进行覆盖,取最后一次的执行; 如果是同时 setState
多个不同的值,在更新时会对其进行合并批量更新。
React.js 18.x
之前的批量处理原理: 通过全局变量 executionContext
控制 React
执行上下文,指示 React
开启同步或者异步更新。executionContext
一开始被初始化为 NoContext
,因此 React
默认是同步更新的。在合成事件、生命周期钩子(除 componentDidUpdate
除外), 中, 一开始这个变量会赋值为一个 BatchedContext
, 在此期间, 如果多次同步调用更新函数, 会走批量更新逻辑, 构造更新队列, 将多个更新函数加入到更新队列, 等到当前环境执行完毕后, 将 executionContext
恢复为之前状态, 根据更新队列进行调度更新。如果异步调用更新函数, 此时 executionContext
已经恢复之前状态, 直接开始更新任务。
React.js 18.x
之后的批量处理原理: 在同一环境中, 无论同步调用还是异步调用, 多次更新函数任务的优先级是相同的, 属于同一批任务。这一批任务都通过 scheduleUpdateOnFiber
标记相同的优先级之后, 开始通过 ensureRootIsScheduled
进行调度更新, 对比上次等待的更新和本次更新的优先级, 如果相等, 则会终止这个这任务的调度流程, 复用已有的调度任务。
3.2 React
中 setState
中什么时候会合并更新?怎么实现链式更新?
-
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;