研究 setState
这个问题来源于一个疑惑:使用 redux 的时候 dispatch
一个 action
,为何能够致使视图的更新?react
首先的猜测是 store
改变后,redux 在某处调用了 setState
,通知了 react。redux
看了下代码发现确实如此,调用 dispatch action
会触发一个 onStateChange
的函数 (这个函数在 connect
的时候就被注册到 store
了, store
被 reducer
修改后触发),onStateChange
函数判断若是须要 shouldComponentUpdate
的话则执行 this.setState({})
来触发 react 更新。数组
那么问题来了:浏览器
setState
可让视图更新,它是如何一步步到 virtualDOM
而后渲染的呢setState
为何有时表现是异步的有时又是同步的?willReceiveProps
里能够setState
而willUpdate
不行?捋了一下流程得出下图,图中每一个流程块冒号前即为被执行的函数:缓存
简要的说一下流程:bash
setState
后将传入的 state
放入队列 queue
,enqueueUpdate
方法会根据 isBatchingUpdate
标志位判断,若当前已经在更新组件则将直接当前组件放入 dirtyComponents
数组,不然将 isBatchingUpdate
置为 true 并开启一个 "批量更新 (batchedUpdates
)" 的事务(transaction
)。简单地说,一个所谓的
Transaction
就是将须要执行的method
使用wrapper
封装起来,再经过Transaction
提供的perform
方法执行。而在perform
以前,先执行全部wrapper
中的initialize
方法;perform
完成以后(即method
执行后)再执行全部的close
方法。一组initialize
及close
方法称为一个wrapper,
Transaction
支持多个wrapper
叠加。app
事务开启后会依次执行 initialize、perform、close
方法。能够看到,batchedUpdates
在 perform
阶段会再次执行 enqueueUpdate
方法,因为这时的 isBatchingUpdate
已是 true 了因此会将当前组件放入 dirtyComponents
。关键就在 close
阶段了,若是 dirtyComponents
为空则表示不须要更新,不然就开始更新,开启 flushBatchedUpdates
事务。异步
flushBatchedUpdates
在 perform
阶段会将 dirtyComponents
中的组件按 父 > 子
组件的顺序调用更新方法,组件在更新的时候会依次执行:willReceiveProps -> 将 queue 中缓存的 state 与缓存的 state 合并 -> shouldComponentUpdate。
复制代码
若是判断须要更新,则执行组件的 render
方法获得新的 reactElement
,将其与以前的 reactElement
作 diff 便可,将 diff 结果(删除,移动等)经过 setInnerHTML
等封装方法更新视图便可,细节可见图。函数
flushBatchedUpdates
在 close
阶段会再次检查 dirtyComponents
长度有没有变化,若是变化了说明存在有新的 dirtyComponent
,须要再来一次 flushBatchedUpdates
。补上 updateComponent
代码:ui
// 更新组件
updateComponent: function(transaction, prevParentElement, nextParentElement) {
var prevContext = this.context;
var prevProps = this.props;
var nextContext = prevContext;
var nextProps = prevProps;
if (prevParentElement !== nextParentElement) {
nextContext = this._processContext(nextParentElement._context);
nextProps = this._processProps(nextParentElement.props);
// 当前状态为 RECEIVING_PROPS
this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS;
// 若是存在 componentWillReceiveProps,则执行
if (this.componentWillReceiveProps) {
this.componentWillReceiveProps(nextProps, nextContext);
}
}
// 设置状态为 null,更新 state
this._compositeLifeCycleState = null;
var nextState = this._pendingState || this.state;
this._pendingState = null;
var shouldUpdate =
this._pendingForceUpdate ||
!this.shouldComponentUpdate ||
this.shouldComponentUpdate(nextProps, nextState, nextContext);
if (!shouldUpdate) {
// 若是肯定组件不更新,仍然要设置 props 和 state
this._currentElement = nextParentElement;
this.props = nextProps;
this.state = nextState;
this.context = nextContext;
this._owner = nextParentElement._owner;
return;
}
this._pendingForceUpdate = false;
......
// 若是存在 componentWillUpdate,则触发
if (this.componentWillUpdate) {
this.componentWillUpdate(nextProps, nextState, nextContext);
}
// render 递归渲染
var nextMarkup = this._renderedComponent.mountComponent(
thisID,
transaction,
this._mountDepth + 1
);
// 若是存在 componentDidUpdate,则触发
if (this.componentDidUpdate) {
transaction.getReactMountReady().enqueue(
this.componentDidUpdate.bind(this, prevProps, prevState, prevContext),
this
);
}
},
复制代码
捋完整个流程能够回答以前一些疑惑:
setState
后紧接着打 log,有时 state
没有马上变,有时候又变了?生命周期中的 setState
处于一个大的 transaction
中,此时的 isBatchingUpdate
为 true
,执行 setState
只会让 dirtyComponents
数组 push 当前组件而不会进一步处理,此时 log 来看的话 state
仍是没有变的。而若是在 transaction 以外,例如 setTimeout
里 setState
,此时 isBatchingUpdate
为 false
,会一路直接执行下来更改 state
,因此此时 log 出来 state
是被马上改变了的。所以 setState 不保证是同步,而不是说它必定是异步。
2. 都在同一个 tranaction
中,为何在 willReceiveProps
时还能够 setState
,而在 shouldComponentUpdate
和 willUpdate
的时候 setState
会致使浏览器死循环?
组件内部有一标志位 _compositeLifeCycleState
表示当前生命周期状态,在 willReceiveProps
前被设置为 RECEIVING_PROPS
,在 willReceiveProps
执行后被设置为 null,而 performUpdateIfNecessary
函数在当前状态为 MOUNTING
或 RECEIVING_PROPS
时不会继续调用 updateComponent
函数。
performUpdateIfNecessary: function(transaction) {
var compositeLifeCycleState = this._compositeLifeCycleState;
// ■■■■■■■■重点■■■■■■■■■■■■
// 当状态为 MOUNTING 或 RECEIVING_PROPS 时,则不更新
if (compositeLifeCycleState === CompositeLifeCycle.MOUNTING ||
compositeLifeCycleState === CompositeLifeCycle.RECEIVING_PROPS) {
return;
}
var prevElement = this._currentElement;
var nextElement = prevElement;
if (this._pendingElement != null) {
nextElement = this._pendingElement;
this._pendingElement = null;
}
// 调用 updateComponent
this.updateComponent(
transaction,
prevElement,
nextElement
);
}
复制代码
所以在 willReceiveProps
时 setState
因为 _compositeLifeCycleState
已是 RECEIVING_PROPS
了,不回触发新的 updateComponent
,而在 willUpdate
的时候 _compositeLifeCycleState
已经被置回 null 了,所以会引起下一次的 updateComponent
,而后就再次触发组件的各生命周期,固然也会免不了执行 willUpdate
,所以进入了死循环。