setState
一、认识
this.setState()
更新 React
Class
组件的 state
二、语法
this.setState(nextState, callback?)
-
nextState
: 一个对象或者函数-
对象: 传递一个对象作为
nextState
,它将浅层合并到this.state
中 -
函数: 是一个纯函数, 函数会将先前的
prevState
、prevProps
作为参数, 并且应该返回要浅层合并到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.then
、setTimeout
或者原生事件处理程序中, 更新将会以同步的方式处理。
React.js 18.x
之后, 几乎所有更新, 包括 Promise.then
、setTimeout
或者原生事件处理程序等, 都将自动批处理。因此, 在 React.js 18.x
之后, 如果需要强制同步更新 setState
, 可以将更新包装在 flushSync
中, 但是这样会损害性能。
setState
批量更新策略:
-
对同
key
多次进行setState
: 如果传入对象, 会其进行覆盖, 取最后一次执行。如果传入函数, 更新函数放入队列, 依次执行, 并计算。 -
对不同
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
中, 函数的话会基于之前的 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
开启调度更新流程。
4.2 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
进行调度更新, 对比上次等待的更新和本次更新的优先级, 如果相等, 则会终止这个这任务的调度流程, 复用已有的调度任务。