setState在React事件处理函数中或React方法中是异步,在setTimeout, Promise等异步方法中或原生事件中是同步。
一些例子
React事件:
class Test extends Component {
state = { count: 0 };
handleIncrease = () => {
this.setState({ count: this.state.count + 1 });
console.log(this.state.count);
this.setState({ count: this.state.count + 1 });
console.log(this.state.count);
};
render() {
console.log("render", this.state.count);
const { count } = this.state;
return (
{count}
);
}
}
如上代码异步更新state,点击button输出0, 0, render 1
。
setTimeout异步方法中:
class Test extends Component {
state = { count: 0 };
handleIncrease = () => {
setTimeout(() => {
this.setState({ count: this.state.count + 1 });
console.log(this.state.count);
this.setState({ count: this.state.count + 1 });
console.log(this.state.count);
});
};
render() {
console.log("render", this.state.count);
const { count } = this.state;
return (
{count}
);
}
}
如上代码同步更新state,点击button输出render 1, 1, render 2, 2
。
原生事件处理函数中:
class Test extends Component {
btnRef = React.createRef();
state = { count: 0 };
handleIncrease = () => {
this.setState({ count: this.state.count + 1 });
console.log(this.state.count);
this.setState({ count: this.state.count + 1 });
console.log(this.state.count);
};
componentDidMount() {
this.btnRef.current.addEventListener("click", this.handleIncrease);
}
render() {
console.log("render", this.state.count);
const { count } = this.state;
return (
{count}
);
}
}
如上代码同步更新state,点击button输出render 1, 1, render 2, 2
。
原理
React中有一个执行上下文变量executionContext
,当executionContext === BatchedContext
时就异步更新state,当executionContext === NoContext
时就同步更新state。
由于在调用React事件回调,React生命周期等React可以检测到的方法的时候,React默认通过batchedUpdates方法做了额外处理:调用方法前设置executionContext = BatchedContext
,调用方法后把执行上下文恢复到同步executionContext = NoContext
,所以方法中的setState是异步更新。
batchedUpdates方法:
let executionContext = NoContext; //默认值
export function batchedUpdates(fn) {
let preExecutionContext = executionContext;
executionContext |= BatchedContext;
fn();
executionContext = preExecutionContext;
}
当setState在异步方法中时,同步方法执行完后才会调用调用异步任务,此时最后一条语句executionContext = preExecutionContext;
已经执行完成,executionContext === NoContext
,这时再执行异步任务中的setState, 就会同步更新。
原生事件中由于没有设置executionContext = BatchedContext
,所以是同步更新state,通过batchedUpdates方法可以实现异步更新。
import React, { Component } from "react";
import { unstable_batchedUpdates } from "react-dom";
class Test extends Component {
btnRef = React.createRef();
state = { count: 0 };
handleIncrease = () => {
this.setState({ count: this.state.count + 1 });
console.log(this.state.count);
this.setState({ count: this.state.count + 1 });
console.log(this.state.count);
};
componentDidMount() {
// this.btnRef.current.addEventListener("click", this.handleIncrease);
this.btnRef.current.addEventListener("click", () =>
unstable_batchedUpdates(this.handleIncrease)
);
}
render() {
console.log("render", this.state.count);
const { count } = this.state;
return (
{count}
);
}
}
export default Test;
Concurrent模式下的setState
Concurrent模式下优化了setState更新方式,默认都会采用异步更新。
总结
- 并发模式setState不管在哪里默认都会批量更新;
- 同步模式用了batchUpdates就是批量更新,没用就是同步更新;
- 同步模式下,事件函数或生命周期函数setState是批量的,是因为使用了batchUpdates。