面试被问起 React 的 setState
是同步还是异步执行,根据长久以来的使用经验理所当然地回答“异步”,结果被告知存在同步执行的情况。遂查看 React 文档,重新学习一下。
用法
setState(updater[, callback])
setState(stateChange[, callback])
setState((state, props) => stateChange[, callback]
) (v0.13 开始支持)
updater
可以是对象或者函数。一般情况下,setState
的更新是异步的,updater
会被放入到队列中,不会立即更新 state,触发重新渲染。
console.log(this.state.quantity); // 1
this.setState({quantity: 2}, () => {
console.log(this.state.quantity); // 2
});
console.log(this.state.quantity); // 1
如果基于原来的 state
生成新 state
,建议传入函数,避免多次调用时的结果不符合预期。
this.setState({count: this.state.count + 1});
this.setState({count: this.state.count + 1});
this.setState((state) => {
return {count: state.count + 1};
});
this.setState((state) => {
return {count: state.count + 1};
});
执行顺序
setState
-> render
-> componentDidUpdate
-> setState
callback
componentDidMount() {
console.log('componentDidMount', this.state);
this.setState({
foo: 'bar'
}, () => {
console.log('setState callback', this.state);
});
}
componentDidUpdate(prevProps, prevState) {
console.log('componentDidUpdate', this.state);
}
render() {
console.log('render', this.state);
return (
<h1>LifecycleDemo</h1>
);
}
多次调用 setState
的效果
(1)通常情况(在 React 的生命周期、合成事件处理函数中调用 setState
)表现为异步
在 React 的生命周期函数中
componentDidMount() {
this.setState({
foo: 'bar',
x: true
});
this.setState({
foo: 'baz',
y: true
});
}
componentDidMount() {
this.setState(() => ({
foo: 'bar',
x: true
}));
this.setState(() => ({
foo: 'baz',
y: true
}));
}
传入函数和传入对象的结果一致,都是批量更新,只触发一次重新渲染。
在 React 的合成事件处理函数中
handleClick () {
console.log('before setState x', this.state);
this.setState({
foo: 'bar',
x: true
});
console.log('after setState x', this.state);
console.log('before setState y', this.state);
this.setState({
foo: 'baz',
y: true
});
console.log('after setState y', this.state);
}
render() {
return (
<button onClick={this.handleClick}>Click Me</button>
);
}
- render 次数:1
- render 时的
state
:{foo: 'baz', x: true, y: true}
- 执行到
render()
前,打印this.state
始终不变
setState
异步执行,多次 setState
只会产生一次批量更新,触发一次重新渲染。
(2)其他情况(在addEventListener
、定时器、Promise
等异步回调函数中调用 setState
)表现为同步
componentDidMount() {
Promise.resolve().then(() => {
console.log('before setState x', this.state);
this.setState({
foo: 'bar',
x: true
});
console.log('after setState x', this.state);
console.log('before setState y', this.state);
this.setState({
foo: 'baz',
y: true
});
console.log('after setState y', this.state);
});
}
componentDidMount() {
setTimeout(() => {
this.setState({
foo: 'bar',
x: true
});
this.setState({
foo: 'baz',
y: true
});
});
}
- render 次数:2
- 两次 render 的
state
:{foo: 'bar', x: true}
{foo: 'baz', x: true, y: true}
setState
同步执行,setState
执行后会立即执行 render()
、componentDidUpdate()
,再执行下一个 setState
。
(3)未来(默认都是批量更新,异步)
Currently (React 16 and earlier), only updates inside React event handlers are batched by default. There is an unstable API to force batching outside of event handlers for rare cases when you need it.
In future versions (probably React 17 and later), React will batch all updates by default so you won't have to think about this. As always, we will announce any changes about this on the React blog and in the release notes.
——摘录自 Dan Abramov 的回答
总结
平时更新 state 时都是手动合并 stateChange,生成最终的 nextState 后,再调用 setState
一次性更新,完美地避开了 setState
行为不一致的地方……从减少心智负担地角度来说,不要依赖 React 实现批量更新,而应该自己手动合并,只调用一次 setState
,保证 setState
异步执行的一致性。
this.setState(stateChange1);
this.setState(stateChange2);
this.setState(stateChange3);
this.setState({
...stateChange1,
...stateChange2,
...stateChange3
});