执行对整棵树的每一个节点进行更新的操作(performUnitOfWork内调用beginWork)
源码在 react-reconciler 下的 ReactFiberBeginWork.js 内:
beginWork()
方法里通过 switch (workInProgress.tag)
对不同的组件做不同的更新处理:
下面看看不同组件的更新:
出现频率很高的一个判断 current === null
用于判断组件是否是第一次渲染。
function updateFunctionComponent(
current,
workInProgress,
Component,
nextProps: any,
renderExpirationTime,
) {
// 此处去掉__DEV__代码
const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
const context = getMaskedContext(workInProgress, unmaskedContext);
let nextChildren;
prepareToReadContext(workInProgress, renderExpirationTime);
prepareToUseHooks(current, workInProgress, renderExpirationTime);
if (__DEV__) {
// 此处去掉__DEV__代码
} else {
nextChildren = Component(nextProps, context);
}
nextChildren = finishHooks(Component, nextProps, nextChildren, context);
// React DevTools reads this flag.
workInProgress.effectTag |= PerformedWork;
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
return workInProgress.child;
}
调用Component
时传入了两个值nextProps
和context
, 那么函数组件可以直接通过第二个参数拿到context
,官方文档没有说明过这点。
function updateClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps,
renderExpirationTime: ExpirationTime,
) {
if (__DEV__) {
// 去掉__DEV__代码
}
// Push context providers early to prevent context stack mismatches.
// During mounting we don't know the child context yet as the instance doesn't exist.
// We will invalidate the child context in finishClassComponent() right after rendering.
let hasContext;
if (isLegacyContextProvider(Component)) {
hasContext = true;
pushLegacyContextProvider(workInProgress);
} else {
hasContext = false;
}
prepareToReadContext(workInProgress, renderExpirationTime);
const instance = workInProgress.stateNode;
let shouldUpdate;
if (instance === null) {
if (current !== null) {
// An class component without an instance only mounts if it suspended
// inside a non- concurrent tree, in an inconsistent state. We want to
// tree it like a new mount, even though an empty version of it already
// committed. Disconnect the alternate pointers.
current.alternate = null;
workInProgress.alternate = null;
// Since this is conceptually a new fiber, schedule a Placement effect
workInProgress.effectTag |= Placement;
}
// In the initial pass we might need to construct the instance.
constructClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
mountClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
shouldUpdate = true;
} else if (current === null) {
// In a resume, we'll already have an instance we can reuse.
shouldUpdate = resumeMountClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
} else {
shouldUpdate = updateClassInstance(
current,
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
}
const nextUnitOfWork = finishClassComponent(
current,
workInProgress,
Component,
shouldUpdate,
hasContext,
renderExpirationTime,
);
if (__DEV__) {
// 去掉__DEV__代码
}
return nextUnitOfWork;
}
开始是对context的操作,因为ClassComponent可以成为context provider。
current === null只会出现在第一次渲染的时候,因为会先创建workInProcess,在渲染结束之后才会把workInProcess拷贝成current,代表着第一次渲染结束。而后面也会出现根据current === null来判断是否需要调用componentDidMount的代码
在这里如果current === null就行要进行实例的构建工作,如果不是,直接updateClassInstance
如果是还要判断实例是否已经创建workInProgress.stateNode === null,如果是的话要创建这个实例,通过constructClassInstance(代码在同级的ReactFiberClassComponent.js内,这个方法调用adoptClassInstance给 ClassComponent 的实例挂载一个updater对象,里面包含我们常用的方法:forceUpdate, replaceState, setState),并且挂载实例mountClassInstance(初始化 props、state 等实例属性,如果有updateQueue就更新之,一般来说第一次渲染是没有的。)
如果已经有current则调用updateClassInstance
最后调用finishClassComponent
FunctionalComponent在含有render方法时会被当做ClassComponent来处理
export default ()=>{
return {
componentDidMount() {
console.log('Indeterminate')
},
render() {
return <div>Indeterminate</div>
},
}
}
原生的html标签(completeWork阶段就是从HostComponent开始逆着Fiber输往回return Fiber,并在HostComponent上进行虚拟DOM的diff判断比较props)
不同类型的组件更新最后都会调用此方法
- 根据props.children生成Fiber子树
- 判断Fiber对象是否可以复用
- 列表根据key优化
export function reconcileChildren(
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any,
renderExpirationTime: ExpirationTime,
) {
if (current === null) {
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderExpirationTime,
);
} else {
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
nextChildren,
renderExpirationTime,
);
}
}
查看上面两种不同调用的方法引入:
import {
mountChildFibers,
reconcileChildFibers,
cloneChildFibers,
} from './ReactChildFiber';
然后去ReactChildFiber.js内查看方法定义: mountChildFibers和reconcileChildFibers方法是一样的,唯一的区别是生成这个方法的时候的一个参数不同
export const reconcileChildFibers = ChildReconciler(true);
export const mountChildFibers = ChildReconciler(false);
这个参数叫shouldTrackSideEffects,他的作用是判断是否要增加一些effectTag,主要是用来优化初次渲染的
if (shouldTrackSideEffects && newFiber.alternate === null) {
newFiber.effectTag = Placement
}
ChildReconciler最终调用的是reconcileChildFibers:
function ChildReconciler(shouldTrackSideEffects) {
// 省略其他代码
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
expirationTime: ExpirationTime,
): Fiber | null {}
return reconcileChildFibers;
}
reconcileChildFibers会根据newChild的不同类型进行对应的处理,最终的返回是当前节点的第一个孩子节点,会在performUnitWork中 return 并赋值给nextUnitOfWork。
children的合法类型:
- ReactElement,通过createElement和ReactDOM.createPortal创建,$$typeof不同
- string或者number,abc中div的children就是"abc"
- [// renderAble]数组,每一项都可以是其他合法类型,不能嵌套
- Iterator,跟数组类似,只是遍历方式不同
- React.ConcurrentMode这些内置组件,最终会转换成ReactElement,不同的是ReactElement.type
- reconcileSingleElement & reconcileSinglePortal & reconcileSingleTextNode
- reconcileChildrenArray & reconcileChildrenArray