当this.setState()被调用时,React会重新调用render方法来重新绘制UI
异步更新
setState通过一个队列机制实现state的更新
当执行setState时,会将需要更新的state合并后放入状态队列,而不是立刻更新
队列机制可以高效的批量更新state,如果不通过setState直接修改this.state的值,将不会放入状态队列中,当下次调用setState并对状态队列进行合并时,将会忽略之前的直接被修改的state,而造成无法预知的错误
setState流程还是很复杂的,设计也很精巧,避免了重复无谓的刷新组件。它的主要流程如下
- enqueueSetState将state放入队列中,并调用enqueueUpdate处理要更新的Component
- 如果组件当前正处于update事务中,则先将Component存入dirtyComponent中。否则调用batchedUpdates处理。
- batchedUpdates发起一次transaction.perform()事务
- 开始执行事务初始化,运行,结束三个阶段
- 初始化:事务初始化阶段没有注册方法,故无方法要执行
- 运行:执行setSate时传入的callback方法,一般不会传callback参数
- 结束:更新isBatchingUpdates为false,并执行FLUSH_BATCHED_UPDATES这个wrapper中的close方法
- FLUSH_BATCHED_UPDATES在close阶段,会循环遍历所有的dirtyComponents,调用updateComponent刷新组件,并执行它的pendingCallbacks, 也就是setState中设置的callback。
循环调用风险
当调用setState时,实际上会执行enqueueSetState方法,并对partialState 以及_pendingStateQueue更新队列进行合并操作,最终通过enqueueSetState执行state更新
而performUpdateIfNecessary方法会获取_pendingElement、_pendingStateQueue、_pendingForceUpdate 并调用receiveComponent和updateComponent方法进行组件更新
如果在shouldComponentUpdate 或 componentWillUpdate方法中调用setSate
此时this._pendingStateQueue != null 则performUpdateIfNecessary方法就会调用updateComponent方法进行组件更新
performUpdateIfNecessary: function (transaction) {
if (this._pendingElement != null) {
// receiveComponent会最终调用到updateComponent,从而刷新View
ReactReconciler.receiveComponent(this, this._pendingElement, transaction, this._context);
} else if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
// 执行updateComponent,从而刷新View。
this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context);
} else {
this._updateBatchNumber = null;
}
}
但updateComponent方法又会调用shouldComponentUpdate 和 componentWillUpdate方法
因此造成循环调用 使得浏览器内存占满奔溃
调用栈
import React,{Component} from 'react';
class Example extends Component {
constructor(){
super();
this.state = {
val:0
}
}
componentDidMount(){
this.setState({
val:this.state.val + 1
})
console.log(this.state.val) // 第1次输出 0
this.setState({
val:this.state.val + 1
})
console.log(this.state.val) // 第2次输出 0
setTimeout(() => {
this.setState({
val:this.state.val + 1
})
console.log(this.state.val) // 第3次输出 2
this.setState({
val:this.state.val + 1
})
console.log(this.state.val) // 第4次输出 3
})
}
}
this.setState()
||
||
\/
newState存入pending队列
||
||
\/
调用enqueueUpdate
||
||
\/
是否处于批量更新模式
|| ||
|| Y || N
\/ \/
将组件保存到 遍历dirtyComponrnts
dirtyComponents 调用updateComponent
更新pending state or props
setState简化调用栈
enqueueUpdate 的代码如下
function enqueueUpdate(component){
ensureInjected();
// 如果不处于批量更新模式
if(!batchingStrategy.isBatchingUpdates){
batchingStrategy.batchedUpdates(enqueueUpdate,component);
return;
}
// 如果处于批量更新模式 则将组件保存在 dirtyComponents中
dirtyComponents.push(component);
}
如果 isBatchingUpdates 为true,则对队列中的更新执行 batchedUpdates 方法
否则只把当前的组件(即调用了setState的组件)放入dirtyComponents数组中
例子中4次setState调用的实现之所以不同 这里逻辑判断起了关键作用