setState 对于大多数人来讲并不陌生,能够说是 react 中高频出现的,可是有些时候可以当即拿到结果,有些时候却不能。那么 setState 究竟是同步仍是异步的呢?javascript
constructor(props) {
super(props);
this.state = {
number: 1,
};
}
componentDidMount() {
this.setState({ number: 17 });
console.log(this.state.number); // 1
this.setState({ number: 27 });
console.log(this.state.number); // 1
}
复制代码
咱们发现 2 次打印结果都是 1,从表象上看,像是异步的操做, 但其实只是异步的表现形式,每次调用 setState 都会触发更新,出于性能考虑,React 会把多个 setState() 调用合并成一个调用,减小从新 render 的次数。java
setTimeout(() => {
this.setState({ number: 17 });
console.log(this.state.number); // 17
}, 0);
复制代码
这时的打印结果又是 17 了,看起来像是同步的操做。react
setState 在面试题中也频繁出现:面试
state = {
number: 1,
};
increment = () => {
console.log("increment setState前的number", this.state.number);
this.setState({
number: this.state.number + 1,
});
console.log("increment setState后的number", this.state.number);
};
triple = () => {
console.log("triple setState前的number", this.state.number);
this.setState({
number: this.state.number + 1,
});
this.setState({
number: this.state.number + 1,
});
this.setState({
number: this.state.number + 1,
});
console.log("triple setState后的number", this.state.number);
};
reduce = () => {
setTimeout(() => {
console.log("reduce setState前的number", this.state.number);
this.setState({
number: this.state.number - 1,
});
console.log("reduce setState后的number", this.state.number);
}, 0);
};
复制代码
render(){
return <div>
<button onClick={this.increment}>点我增长</button>
<button onClick={this.triple}>点我增长三倍</button>
<button onClick={this.reduce}>点我减小</button>
</div>
}
复制代码
这 3 个按钮依次点击的打印结果是什么呢?算法
打印结果依次为:性能优化
increment setState前的number 1
increment setState后的number 1
triple setState前的number 2
triple setState后的number 2
reduce setState前的number 3
reduce setState后的number 2
复制代码
你答对了吗?(若是答错的话,面试就挂啦)markdown
那么 setState 究竟是同步仍是异步的呢?接下来咱们来看一下调用 setState 以后发生了什么。异步
在代码中调用 setState 函数以后,React 会将传入的参数对象与组件当前的状态合并,而后触发所谓的调和过程(Reconciliation)。通过调和过程,React 会以相对高效的方式根据新的状态构建 React 元素树而且着手从新渲染整个 UI 界面。在 React 获得元素树以后,React 会自动计算出新的树与老树的节点差别,而后根据差别对界面进行最小化重渲染。在差别计算算法中,React 可以相对精确地知道哪些位置发生了改变以及应该如何改变,这就保证了按需更新,而不是所有从新渲染。函数
setState 工做流为:性能
isBatchingUpdates 是一个 react 全局惟一的变量,初始值是 false,意味着“当前并未进行任何批量更新操做”,每当 React 去执行更新动做时,会将 isBatchingUpdates 置为 true,表示如今正处于批量更新过程当中。置为 true 时,任何须要更新的组件都只能暂时进入 dirtyComponents 里排队等候下一次的批量更新。 isBatchingUpdates 这个变量,在 React 的生命周期函数以及合成事件执行前,已经被 React 悄悄修改成了 true,这时咱们所作的 setState 操做天然不会当即生效。当函数执行完毕后,事务的 close 方法会再把 isBatchingUpdates 改成 false。 由于 isBatchingUpdates 是在同步代码中变化的,而 setTimeout 的逻辑是异步执行的。当 this.setState 调用真正发生的时候,isBatchingUpdates 早已经被重置为了 false,这就使得当前场景下的 setState 具有了马上发起同步更新的能力。
了解了 setState 的实现方式以后,咱们来简单的模拟实现一个 setState,总结一下,主要是要实现 2 个功能:
调用 setState 以后首先执行了 enqueueSetState 方法
setState( stateChange ) {
enqueueSetState( stateChange, this );
}
复制代码
为了合并 setState,咱们须要一个队列来保存每次 setState 的数据,而后在一段时间后,清空这个队列并渲染组件。
const queue = [];
const renderQueue = [];
function enqueueSetState(stateChange, component) {
// 将新的 state 放进组件的状态队列里
queue.push({
stateChange,
component,
});
// 若是renderQueue里没有当前组件,则添加到队列中
if (!renderQueue.some((item) => item === component)) {
renderQueue.push(component);
}
// 根据 this 拿到对应的组件实例
var internalInstance = getInternalInstanceReadyForUpdate(
component,
"setState"
);
// 用 enqueueUpdate 来处理将要更新的实例对象
enqueueUpdate(internalInstance);
}
复制代码
而后咱们来实现 enqueueUpdate 方法
enqueueUpdate(component) {
if (!batchingStrategy.isBatchingUpdates) {
// 若当前没有处于批量建立/更新组件的阶段,则当即更新组件
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
// 不然,先把组件塞入 dirtyComponents 队列里,让它“再等等”
dirtyComponents.push(component);
}
复制代码
setState( stateChange,callback ) {
enqueueSetState( stateChange,this );
if(callback) {
this.enqueueCallback(this, callback, 'setState')
}
}
复制代码
那么咱们如何同步的获取到更新后的数据呢?在官方文档中有所说明:
setState() 并不老是当即更新组件。它会批量推迟更新。这使得在调用 setState() 后当即读取 this.state 成为了隐患。为了消除隐患,请使用 componentDidUpdate 或者 setState 的回调函数(setState(updater, callback)),这两种方式均可以保证在应用更新后触发。
this.setState({ number: 17 }, () => {
console.log(this.state.number); // 17
});
复制代码
setState 自己并非异步,只是由于 react 的性能优化机制体现为异步。在 react 的生命周期函数或者合成事件下为异步,在 DOM 原生事件下以及 setTimeOut 为同步。
这里所说的同步异步,并非真正的同步异步,它仍是同步执行的。
这里的异步指的是多个 state 会合并到一块儿进行批量更新。
但愿初学者不要被误导。