这是一道 React 送命题,考察面试者对 React 运行机制的理解程度。
🔢 经典原题
class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
}
}
componentDidMount() {
this.setState({ count: this.state.count + 1 });
console.log(this.state.count); // => 0
this.setState({ count: this.state.count + 1 });
console.log(this.state.count); // => 0
setTimeout(() => {
this.setState({ count: this.state.count + 1 });
console.log(this.state.count); // => 1 + 1 = 2
this.setState({ count: this.state.count + 1 });
console.log(this.state.count); // => 2 + 1 = 3
}, 0)
}
}
💯 输出结果
0 0 2 3
❓ 参考解析
React 为了优化渲染性能,通过 isBatchingUpdates 实现合并操作并延迟更新 UI state 的策略。但是具体 setState 是同步更新还是异步更新,还需要结合具体场景判断。
异步更新:React 可以控制的区域(生命周期 componentDidiMount ...,合成事件 onClick、onChange...)
componentDidMount() {
// 异步状态更新
}
同步更新:React API 以外的原生操作(addEventListener、setTimeout、setInterval)
setTimeout(() => {
// 同步状态更新
}, 0)
📑 源码解读
setState 继承至 React.Component,会调用 enqueueSetState
ReactComponent.prototype.setState = function (partialState, callback) {
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState'); 📌
}
};
enqueueSetState 函数将状态加入待更新状态队列:
enqueueSetState: function (publicInstance, partialState) {
// 根据 this 拿到对应的组件实例
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
// 这个 queue 对应的就是一个组件实例的 state 数组
var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
queue.push(partialState);
// enqueueUpdate 用来处理当前的组件实例
enqueueUpdate(internalInstance); 📌
}
enqueueUpdate 函数将组件实例加入待更新组件队列:
function enqueueUpdate(component) {
ensureInjected();
// 注意这一句是问题的关键,isBatchingUpdates标识着当前是否处于批量创建/更新组件的阶段
if (!batchingStrategy.isBatchingUpdates) { 📌
// 若当前没有处于批量创建/更新组件的阶段,则立即更新组件
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
// 否则,先把组件塞入 dirtyComponents 队列里,让它“再等等”
dirtyComponents.push(component);
if (component._updateBatchNumber == null) {
component._updateBatchNumber = updateBatchNumber + 1;
}
}
isBatchingUpdates 表示是否处于更新状态:
var ReactDefaultBatchingStrategy = {
// 全局唯一的锁标识
isBatchingUpdates: false, 📌
// 发起更新动作的方法
batchedUpdates: function(callback, a, b, c, d, e) {
// 缓存锁变量
var alreadyBatchingStrategy = ReactDefaultBatchingStrategy. isBatchingUpdates
// 把锁“锁上”
ReactDefaultBatchingStrategy. isBatchingUpdates = true
if (alreadyBatchingStrategy) {
callback(a, b, c, d, e)
} else {
// 启动事务,将 callback 放进事务里执行
transaction.perform(callback, null, a, b, c, d, e) 📌
}
}
}
委托 Transaction 事务进行更新操作:
* wrappers (injected at creation time)
* + +
* | |
* +-----------------|--------|--------------+
* | v | |
* | +---------------+ | |
* | +--| wrapper1 |---|----+ |
* | | +---------------+ v | |
* | | +-------------+ | |
* | | +----| wrapper2 |--------+ |
* | | | +-------------+ | | |
* | | | | | |
* | v v v v | wrapper
* | +---+ +---+ +---------+ +---+ +---+ | invariants
* perform(anyMethod) | | | | | | | | | | | | maintained
* +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | | | | | | | | | | | |
* | +---+ +---+ +---------+ +---+ +---+ |
* | initialize close |
* +-----------------------------------------+
形成锁的开关机制,实现 state 收集,批量合并更新操作,表现为异步:
add = () => {
// 进来先锁上
isBatchingUpdates = true 📌
console.log('add前', this.state.num)
this.setState({
num: this.state.num + 1
});
console.log('add后', this.state.num)
// 执行完函数再放开
isBatchingUpdates = false 📌
}
由于 setTimeout 为异步的宏任务,当回调执行时,实际上 isBatchingUpdates 已经为 false,
因此在效果上,表现为同步:
reduce = () => {
// 进来先锁上
isBatchingUpdates = true 📌
setTimeout(() => {
console.log('reduce前的', this.state.num)
this.setState({
num: this.state.num - 1
});
console.log('reduce后的', this.state.num)
},0);
// 执行完函数再放开
isBatchingUpdates = false 📌
}