React深入分析 (2)更新过程
Opened this issue · 0 comments
主要分析一下React处理更新和构建effect list
demo code
class App extends React.Component {
constructor(props) {
super(props);
this.state = {count: 0};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState((state) => {
return {count: state.count + 1};
});
this.setState((state) => {
return {count: state.count + 2};
});
}
componentDidUpdate() {}
render() {
return (<React.Fragment>
<button key="1" onClick={this.handleClick}>点我</button>
<span key="2">{this.state.count}</span>
</React.Fragment>
)
}
}
整个Reconcliation分为render 和 commit两个阶段
render阶段
- 更新App的state对象的count属性
- 调用render方法,生成children,对children进行reconcilation.
- 更新span的属性
commit阶段
- 更新span 的textContent属性
- 调用componentDidUpdate生命周期
render 阶段
当我们点击按钮时click事件触发,会调用事件回调函数,本例子中调用两次setState,会进入批量处理。
每一个React Class Component都有一个updater,默认情况下,update对象是ReactNoopUpdateQueue,一个抽象的API表示,并没有什么功能。
function Component(props, context, updater) {
this.props = props;
this.context = context;
// If a component has string refs, we will assign a different object later.
this.refs = emptyObject;
// We initialize the default updater but the real one gets injected by the
// renderer.
this.updater = updater || ReactNoopUpdateQueue;
}
Component.prototype.setState = function (partialState, callback) {
!(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? invariant(false, 'setState(...): takes an object of state variables to update or a function which returns an object of state variables.') : void 0;
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
当Class组件实例化时,实例的updater会赋予具体的实现,这种做法使得在ReactDOM, React Native, SSR ,testRender等环境有不同的setState实现机制。
在ReactDOM中,创建组件实例时会设置实例的uptater为classComponentUpdater,它主要负责处理更新队列和调度工作。
function adoptClassInstance(workInProgress, instance) {
instance.updater = classComponentUpdater;
workInProgress.stateNode = instance;
}
var classComponentUpdater={
isMounted: isMounted,
enqueueSetState: function (inst, payload, callback) {},
enqueueReplaceState: function (inst, payload, callback) {},
enqueueForceUpdate: function (inst, callback) {}
}
对于本demo而言,调用setState时会调用this.updater.enqueueSetState方法。
enqueueSetState: function (inst, payload, callback) {
var fiber = get(inst); //获取实例的fibernode
var currentTime = requestCurrentTime();
var expirationTime = computeExpirationForFiber(currentTime, fiber);
//创建更新队列
var update = createUpdate(expirationTime);
update.payload = payload;
if (callback !== undefined && callback !== null) {
{
warnOnInvalidCallback$1(callback, 'setState');
}
update.callback = callback;
}
flushPassiveEffects();
enqueueUpdate(fiber, update);
scheduleWork(fiber, expirationTime);
},
首先获取当前组件的fiber node,创建更新队列update,用payload更新update.payload,payload是setState方法的参数。可以是函数或者对象。
本例子中是一个函数。
update={
callback: null
expirationTime: 1
next: null
nextEffect: null
payload:
tag: 0
}
update.payload = payload;
接下来调用enqueueUpdate,传入fiber, update对象,这一步是处理更新队列,将更新添加到fiber 的updateQueue中,第一次调用setState后updateQueue的结构如下
{
stateNode:App, //实例
type:ƒ App(props),//构造函数
updateQueue:{
baseState: {count: 0},
firstUpdate: {
expirationTime: 1,
tag: 0,
payload: ƒ (state),
callback: null,
next: null,
},
lastUpdate:{
expirationTime: 1,
tag: 0,
payload: ƒ (state),//(state) => { return {count: state.count + 1} }
callback: null,
next: null,
},
}
}
可以看到updateQueue.firstUpdate.payload就是setStae传入的回调函数。
由于进入了批量处理,接着会调用第二个setState方法。
{
stateNode:App, //实例
type:ƒ App(props),//构造函数
updateQueue:{
baseState: {count: 0},
firstUpdate: {
expirationTime: 1,
tag: 0,
payload: ƒ (state),
callback: null,
next: {
callback: null
expirationTime: 1
next: null
nextEffect: null,
////(state) => { return {count: state.count + 2} }
payload: ƒ (state)
},
},
lastUpdate:{
expirationTime: 1,
tag: 0,
payload: ƒ (state),//(state) => { return {count: state.count + 2} }
callback: null,
next: null,
},
}
}
处理完更新队列之后就开始scheduleWork了。
从根节点HostRoot Fiber node开始调用renderRoot方法 ,
根据root.current创建nextUnitOfWork,然后开始工作,所有的工作都在fiber node的alertnate上进行,如果alertnate不存在,React会调用createWorkInProgress 创建,然后处理更新。
需要注意的是它会跳过(跳过)已经处理过的fiber节点,直到它找到一个未完成工作的节点,本示例中是App fiber node.
如果当前节点没有要完成的工作,会直接克隆子节点并返回,作为下一个工作单元。
function bailoutOnAlreadyFinishedWork(current$$1, workInProgress, renderExpirationTime) {
cloneChildFibers(current$$1, workInProgress);
return workInProgress.child;
}
beginWork
beginWork方法是一个有很多case的switch方法,
function beginWork(current$$1, workInProgress, renderExpirationTime) {
switch (workInProgress.tag) {
case IndeterminateComponent:
...
case LazyComponent:
...
case FunctionComponent:
...
case ClassComponent:
return updateClassComponent(current$$1, workInProgress, _Component2, _resolvedProps, renderExpirationTime);
case HostRoot:
...
case HostComponent:
...
case HostText:
...
}
显然App是ClassComponent,会调用ClassComponent对应的处理方法。
updateClassComponent 处理App组件的更新
移除部分无关代码。
function updateClassComponent(current$$1, workInProgress, Component, nextProps, renderExpirationTime) {
var instance = workInProgress.stateNode;
var shouldUpdate = void 0;
shouldUpdate = updateClassInstance(current$$1, workInProgress, Component, nextProps, renderExpirationTime);
return finishClassComponent(current$$1, workInProgress, Component, shouldUpdate, hasContext, renderExpirationTime);
}
function updateClassInstance(current, workInProgress, ctor, newProps, renderExpirationTime) {
//获取实例
var instance = workInProgress.stateNode;
var oldProps = workInProgress.memoizedProps;
instance.props = oldProps;
var getDerivedStateFromProps = ctor.getDerivedStateFromProps;
var oldState = workInProgress.memoizedState;
var newState = instance.state = oldState;
var updateQueue = workInProgress.updateQueue;
//处理updateQueue
if (updateQueue !== null) {
processUpdateQueue(workInProgress, updateQueue, newProps, instance, renderExpirationTime);
newState = workInProgress.memoizedState;
}
//调用getDerivedStateFromProps
if (typeof getDerivedStateFromProps === 'function') {
applyDerivedStateFromProps(workInProgress, ctor, getDerivedStateFromProps, newProps);
newState = workInProgress.memoizedState;
}
//设置shouldUpdate,会调用shoulComponentUpdate方法
var shouldUpdate = checkHasForceUpdateAfterProcessing() || checkShouldComponentUpdate(workInProgress, ctor, oldProps, newProps, oldState, newState, nextContext);
//标记effectTag
if (shouldUpdate) {
instance.componentWillUpdate(newProps, newState, nextContext);
workInProgress.effectTag |= Update;
workInProgress.effectTag |= Snapshot;
}
//更新实例的props,state,context
instance.props = newProps;
instance.state = newState;
instance.context = nextContext;
return shouldUpdate;
}
这里重点看一下processUpdateQueue 方法,如何处理更新队列的。
在processUpdateQueue内部通过循环遍历firstUpdate进行处理,getStateFromUpdate方法处理单个update
function getStateFromUpdate(workInProgress, queue, update, prevState, nextProps, instance) {
switch (update.tag) {
case ReplaceState:
....
case CaptureUpdate:
...
case UpdateState:
{
var _payload2 = update.payload;
var partialState = void 0;
if (typeof _payload2 === 'function') {
// Updater function
partialState = _payload2.call(instance, prevState, nextProps);
} else {
// Partial state object
partialState = _payload2;
}
if (partialState === null || partialState === undefined) {
// Null and undefined are treated as no-ops.
return prevState;
}
// Merge the partial state and the previous state.
return _assign({}, prevState, partialState);
}
case ForceUpdate:
{
hasForceUpdate = true;
return prevState;
}
}
return prevState;
}
当处理完成之后
{
stateNode:App, //实例
type:ƒ App(props),//构造函数
memoizedState: {count: 3},
effectTag: 4,
updateQueue:{
baseState: {count: 3},
firstUpdate:null,
lastUpdate:null
}
}
更新处理完成之后,实例的state,props,context就被更新了,接下来调用finishClassComponent方法进一步处理。
reconlication children
finishClassComponent 方法会调用组件的render方法生成children,并返回第一个child作为nextUnitOfWork,并用render方法中的数据更新子节点的属性。
由于button节点没有任何更新,我们略过,看span节点
这是reconcile App fiber 的children之前的状态
{
stateNode: new HTMLSpanElement,
type: "span",
key: "2",
memoizedProps: {children: 0},
pendingProps: {children: 0},
...
}
这是render方法调用之后返回的span element ,
{
$$typeof: Symbol(react.element)
key: "2"
props: {children: 3}
ref: null
type: "span"
}
可以看出我们需要的更新数据在render方法返回的element中,在createWorkInProgress方法中,React会拷贝React element的属性去更新fiber node。
{
stateNode: new HTMLSpanElement,
type: "span",
key: "2",
memoizedProps: {children: 0},
pendingProps: {children: 3},
...
}
之后React在span Fiber node上工作时,会将pengingProps拷贝给memoizedProps,然后标记effect,以便将来更新DOM.
nextUnitOfWork= span fiber node
当nextUnitOfWork指向span Fiber node时,将进入下一个workloop。
由于span是HostComponent,beginWork会调用updateHostComponent ,这个方法会对span的子节点进行reconlication,由于span并不存在child,在beginWork工作完成之后,
更新memoizedProps。
function performUnitOfWork(workInProgress) {
...
next = beginWork(current$$1, workInProgress, nextRenderExpirationTime);
workInProgress.memoizedProps = workInProgress.pendingProps;
...
}
然后进入completeWork
completeWork on span
如同beginWork一样,这里也是一个大switch;
switch (workInProgress.tag) {
case IndeterminateComponent:
break;
case LazyComponent:
break;
case SimpleMemoComponent:
case FunctionComponent:
break;
case ClassComponent:
break
case HostComponent:
updateHostComponent$1(current, workInProgress, type, newProps, rootContainerInstance);
....
}
可以看到函数式组件,class组件直接跳过了,因为这一步的工作是处理DOM更新的。
对于我们的span 会做这些工作
- prepareUpdate 处理更新,生成updatePayload,主要是diffProperties
- 添加updateQueue到fiber
- 添加effect
处理后span fiber相关的属性如下
{
stateNode: new HTMLSpanElement,
type: "span",
effectTag: 4,
updateQueue: ["children", "3"],
...
}
现在span fiber node上的工作完成了,开始往父级fiber上收集effect。
Effects list
由于只有App 和span 这两个fiber node 有side effects,React会将span fiber node 添加到其returnFiber.firstEffect
if (effectTag > PerformedWork) {
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = workInProgress;
} else {
returnFiber.firstEffect = workInProgress;
}
returnFiber.lastEffect = workInProgress;
}
最后的effectList如下:
Commit阶段
这个阶段从completeRoot方法开始,一旦开始工作,就不能被打断,直到工作完成。
这个阶段主要工作有两个
- 更新DOM
- 调用生命周期 componentDidUpdate
应用更新
三个大循环在前几篇文章中有提到过,简化一下就是下面这样
function commitRoot(root, finishedWork) {
//getSnapshotBeforeUpdate
commitBeforeMutationLifecycles()
//应用更新
commitAllHostEffects();
root.current = finishedWork;
//调用生命周期
commitAllLifeCycles();
}
这里着重看一下后两个阶段
DOM 更新
这个阶段会调用commitAllHostEffects,这是React将span的text从0改变为3的过程,
function commitAllHostEffects() {
while (nextEffect !== null) {
var primaryEffectTag = effectTag & (Placement | Update | Deletion);
switch (primaryEffectTag) {
case Placement:
{
commitPlacement(nextEffect);
nextEffect.effectTag &= ~Placement;
break;
}
case PlacementAndUpdate:
....
case Update:
{
var _current2 = nextEffect.alternate;
commitWork(_current2, nextEffect);
break;
}
case Deletion:
{
commitDeletion(nextEffect);
break;
}
}
nextEffect = nextEffect.nextEffect;
}
}
对于span fiber node 会进入Update case,而commitWork接着调用updateDOMProperties更新属性
function updateDOMProperties(domElement, updatePayload, ...) {
for (let i = 0; i < updatePayload.length; i += 2) {
const propKey = updatePayload[i];
const propValue = updatePayload[i + 1];
if (propKey === STYLE) { ...}
else if (propKey === DANGEROUSLY_SET_INNER_HTML) {...}
else if (propKey === CHILDREN) {
setTextContent(domElement, propValue);
} else {...}
}
}
DOM更新之后,设置root.current 指向finishedWork,也就是当前工作树。
root.current = finishedWork;
调用生命周期
最后一个方法是调用生命周期钩子。由于在render阶段给App添加了Update effect,将在这个方法里调用App的componentDidUpdate钩子
function commitAllLifeCycles(finishedRoot, ...) {
while (nextEffect !== null) {
const effectTag = nextEffect.effectTag;
if (effectTag & (Update | Callback)) {
const current = nextEffect.alternate;
commitLifeCycles(finishedRoot, current, nextEffect, ...);
}
if (effectTag & Ref) {
commitAttachRef(nextEffect);
}
nextEffect = nextEffect.nextEffect;
}
}
最终会调用commitLifeCycles
function commitLifeCycles(finishedRoot, current, ...) {
...
switch (finishedWork.tag) {
case FunctionComponent: {...}
case ClassComponent: {
const instance = finishedWork.stateNode;
if (finishedWork.effectTag & Update) {
if (current === null) {
instance.componentDidMount();
} else {
...
instance.componentDidUpdate(prevProps, prevState, ...);
}
}
}
case HostComponent: {...}
case ...
}
可以看到,当current不存在时会调用componentDidMount,
存在是会调用componentDidUpdate。
end
进行收尾清理
比如
root.expirationTime = expirationTime;
root.finishedWork = null;
isRendering = false;
以上就是更新的基本过程。
后期会研究一下context和hooks原理