若排除其它的操作项,每一次workLoop都有performUnitOfWork,每一个work有 beginWork和completeWork,可以将workLoop简单看成:
----beginWork
----------beginWork
----------------beginWork
----------------completeWork
----------------beginWork
----------------completeWork
----------------beginWork
----------------------beginWork
----------------------------beginWork
----------------------------completeWork
----------------------------beginWork
----------------------------completeWork
----------------------------beginWork
----------------------------------beginWork
.........省略...
----------------------------------completeWork
----------------------------completeWork
----------------------completeWork
----------------completeWork
----------completeWork
----completeWork
beginWork是创FiberNode,completeWork结束FiberNode。当beginWork返回null,标志着这个节点没有子元素,可以使用completeWork完成work。
function performUnitOfWork(workInProgress) {
//...
next = beginWork(current$$1, workInProgress, nextRenderExpirationTime);
//...
if (next === null) {
// If this doesn't spawn new work, complete the current work.
next = completeUnitOfWork(workInProgress);
}
//...
return next;
}
completeUnitOfWork
尝试完成当前的work,然后移动到下一个sibling。如果没有更多的sibling fiber,返回到parent fiber。
function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
while (true) {
// The current, flushed, state of this fiber is the alternate.
const current = workInProgress.alternate;
const returnFiber = workInProgress.return;
const siblingFiber = workInProgress.sibling;
if ((workInProgress.effectTag & Incomplete) === NoEffect) {
// 完成当前fiber.
nextUnitOfWork = workInProgress;// 以便回滚
if (enableProfilerTimer) {
if (workInProgress.mode & ProfileMode) { startProfilerTimer(workInProgress); }
nextUnitOfWork = completeWork(current, workInProgress,nextRenderExpirationTime);
if (workInProgress.mode & ProfileMode) {// 假设我们没有错误,更新渲染持续时间
stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
}
} else {
nextUnitOfWork = completeWork(current,workInProgress,nextRenderExpirationTime);
}
stopWorkTimer(workInProgress);
resetChildExpirationTime(workInProgress, nextRenderExpirationTime);
if (nextUnitOfWork !== null) {
// Completing this fiber spawned new work. Work on that next.
return nextUnitOfWork;
}
// 将effect记录到当前fiber的父级
if (returnFiber !== null && (returnFiber.effectTag & Incomplete) === NoEffect
) {
if (returnFiber.firstEffect === null) {
returnFiber.firstEffect = workInProgress.firstEffect;
}
if (workInProgress.lastEffect !== null) {
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
}
returnFiber.lastEffect = workInProgress.lastEffect;
}
// 当前fiber有effect,给上一个effect添加nextEffect,形成effect链表
const effectTag = workInProgress.effectTag;
if (effectTag > PerformedWork) {
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = workInProgress;
} else {
returnFiber.firstEffect = workInProgress;
}
returnFiber.lastEffect = workInProgress;//记录最后一个
}
}
if (siblingFiber !== null) {
return siblingFiber;
} else if (returnFiber !== null) {
// If there's no more work in this returnFiber. Complete the returnFiber.
workInProgress = returnFiber;
continue;
} else { return null; // We've reached the root. }
} else { //出错暂时处理 }
}
return null;
}
completeUnitOfWork 可分为 complete Work 和 建立 Effect链表。
completeWork
此时fiber只是一个fiberNode,在complete Work中完成dom Node。
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case IndeterminateComponent:break;
case LazyComponent:break;
case SimpleMemoComponent:
case FunctionComponent:
break;
case ClassComponent: {
const Component = workInProgress.type;
if (isLegacyContextProvider(Component)) {
popLegacyContext(workInProgress);
}
break;
}
case HostRoot: {
// 代码见下面的HostRoot
break;
}
case HostComponent: {
// 代码见下面的HostComponent
break;
}
case HostText: {
// 代码见下面的HostText
break;
}
case ForwardRef:break;
case SuspenseComponent: {
//此处不做讲解,省略代码...
break;
}
case Fragment:break;
case Mode:break;
case Profiler:break;
case HostPortal:
popHostContainer(workInProgress);
updateHostContainer(workInProgress);
break;
case ContextProvider:
popProvider(workInProgress);// Pop provider fiber
break;
case ContextConsumer:break;
case MemoComponent:break;
case IncompleteClassComponent: {
// Same as class component case. I put it down here so that the tags are
// sequential to ensure this switch is compiled to a jump table.
const Component = workInProgress.type;
if (isLegacyContextProvider(Component)) {
popLegacyContext(workInProgress);
}
break;
}
default:
invariant(
false,
'Unknown unit of work tag. This error is likely caused by a bug in ' +
'React. Please file an issue.',
);
}
completeWork 中有实质操作的是HostRoot、HostComponent、HostText、SuspenseComponent;ClassComponent、HostPortal、ContextProvider、IncompleteClassComponent只有pop操作,也就是为下一个HostComponent或HostText铺垫环境。HostText 对应的是Text,HostComponent 对应的是dom 节点,SuspenseComponent 对应的是懒加载组件。一个组件拆解到最后基本都是HostComponent、HostText。
HostText
如果 dom 是树,那么文本就是叶子。
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case HostText: {
let newText = newProps;
if (current && workInProgress.stateNode != null) {
const oldText = current.memoizedProps;
// 若文本更新,添加 side-effect
updateHostText(current, workInProgress, oldText, newText);
} else {
if (typeof newText !== 'string') {
// This can happen when we abort work.
}
const rootContainerInstance = getRootHostContainer();
const currentHostContext = getHostContext();
let wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
if (prepareToHydrateHostTextInstance(workInProgress)) {
markUpdate(workInProgress);
}
} else {
workInProgress.stateNode = createTextInstance(newText, rootContainerInstance,
currentHostContext, workInProgress);
}
}
break;
}
}
在initial mount 时调用 ReactFiberHostConfig 中 createTextInstance来创建文本实例。
updateHostText
workInProgress.stateNode是记录当前fiber的节点,如果有值则调用 updateHostText 标记更新。updateHostTexty依据情况而调用。
if(supportsMutation){
updateHostText = function(current: Fiber,workInProgress: Fiber,
oldText: string,newText: string,
) {
// If the text differs, mark it as an update. All the work in done in commitWork.
if (oldText !== newText) {
markUpdate(workInProgress);
}
};
}else if(supportsPersistence){
updateHostText = function(current: Fiber,workInProgress: Fiber,
oldText: string,
newText: string
) {
if (oldText !== newText) {
// 如果文本内容不同,我们将为它创建一个新的文本实例
const rootContainerInstance = getRootHostContainer();
const currentHostContext = getHostContext();
workInProgress.stateNode = createTextInstance(newText,rootContainerInstance,
currentHostContext,workInProgress);
markUpdate(workInProgress);
}
}
}
//标记更新
function markUpdate(workInProgress: Fiber) {
workInProgress.effectTag |= Update;
}
若text值发生变化则调用 markUpdate 标记fiber。
HostComponent
HostComponet基本上是dom节点。
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case HostComponent: {
popHostContext(workInProgress);
const rootContainerInstance = getRootHostContainer();
const type = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent(current,workInProgress,type,newProps,rootContainerInstance);
if (current.ref !== workInProgress.ref) { markRef(workInProgress); }
} else {
if (!newProps) {// This can happen when we abort work.
break;
}
const currentHostContext = getHostContext();
let wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
if (prepareToHydrateHostInstance(workInProgress,rootContainerInstance,
currentHostContext)
) {
markUpdate(workInProgress);
}
} else {
let instance = createInstance( type,newProps,rootContainerInstance,
currentHostContext,workInProgress);
appendAllChildren(instance, workInProgress, false, false);
// 某些呈现程序需要初始挂载的提交时effect.没有 props.autoFocus 为true则标记更新。
if (finalizeInitialChildren(instance,type,newProps,
rootContainerInstance,currentHostContext)
) {
markUpdate(workInProgress);
}
workInProgress.stateNode = instance;
}
if (workInProgress.ref !== null) {
markRef(workInProgress);
}
}
break;
}
}
在initial mount阶段会 createInstance、appendAllChildren、finalizeInitialChildren、markRef。createInstance检查并创建 dom 节点、appendAllChildren 追加所有字元素、finalizeInitialChildren 中设置dom的props。
绑定属性
修下App示例中的代码,如下所示:
class App extends Component {
render() {
return <div id='appId' onClick={ ()=>{} } className='red'>Hello World!</div>;
}
}
id、className、onClick等只是div(HostComponent) 上的props。completeWork div时 createInstance会创建div实例、appendAllChildren会追加子元素,finalizeInitialChildren 会添加 props。
在setInitialDOMProperties中循环设置具体属性,shouldAutoFocusHostComponent中判断是否获取焦点。
export function finalizeInitialChildren(domElement,type,props,
rootContainerInstance,hostContext){
setInitialProperties(domElement, type, props, rootContainerInstance);
return shouldAutoFocusHostComponent(type, props);
}
export function setInitialProperties(domElement,tag,rawProps,rootContainerElement){
const isCustomComponentTag = isCustomComponent(tag, rawProps);
//...省略 react子遇到一dom元素时会绑定一些事件,例如为input元素绑定onChange事件。
assertValidProps(tag, props);
setInitialDOMProperties(tag,domElement,rootContainerElement,props,isCustomComponentTag);
switch (tag) {
// 省略...其它操作
default:
if (typeof props.onClick === 'function') {//绑定一个 function
trapClickOnNonInteractiveElement(((domElement: any): HTMLElement));
}
break;
}
}
// packages\react-dom\src\client\ReactDOMComponent.js
function setInitialDOMProperties(tag,domElement, rootContainerElement,
nextProps,isCustomComponentTag){
for (const propKey in nextProps) {//循环添加属性
if (!nextProps.hasOwnProperty(propKey)) {
continue;
}
const nextProp = nextProps[propKey];
if (propKey === STYLE) {//style
setValueForStyles(domElement, nextProp);
} else if (propKey === DANGEROUSLY_SET_INNER_HTML){//html
const nextHtml = nextProp ? nextProp[HTML] : undefined;
if (nextHtml != null) {
setInnerHTML(domElement, nextHtml);
}
} else if (propKey === CHILDREN) {//children
if (typeof nextProp === 'string') {
const canSetTextContent = tag !== 'textarea' || nextProp !== '';
if (canSetTextContent) {
setTextContent(domElement, nextProp);
}
} else if (typeof nextProp === 'number') {
setTextContent(domElement, '' + nextProp);
}
} else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
propKey === SUPPRESS_HYDRATION_WARNING
) {
} else if (propKey === AUTOFOCUS) {
} else if (registrationNameModules.hasOwnProperty(propKey)) {//绑定事件
if (nextProp != null) {
ensureListeningTo(rootContainerElement, propKey);//绑定事件
}
} else if (nextProp != null) {
setValueForProperty(domElement, propKey, nextProp, isCustomComponentTag);
}
}
}
在setInitialDOMProperties中setValueForStyles、setInnerHTML、setTextContent、setValueForProperty、ensureListeningTo等,基本上包括了dom props上的各种情况。
updateHostComponent
if (supportsMutation) {
updateHostComponent = function(current: Fiber,workInProgress: Fiber,type: Type,
newProps: Props,
rootContainerInstance: Container
) {
const oldProps = current.memoizedProps;
if (oldProps === newProps) {return;}
const instance: Instance = workInProgress.stateNode;
const currentHostContext = getHostContext();
const updatePayload = prepareUpdate(
instance,type,oldProps,newProps,rootContainerInstance,currentHostContext
);
workInProgress.updateQueue = (updatePayload: any);
if (updatePayload) {
markUpdate(workInProgress);
}
};
}else if(supportsPersistence){
updateHostComponent = function(current,workInProgress,type,newProps,
rootContainerInstance
) {
const currentInstance = current.stateNode;
const oldProps = current.memoizedProps;
const childrenUnchanged = workInProgress.firstEffect === null;
if (childrenUnchanged && oldProps === newProps) {
workInProgress.stateNode = currentInstance;
return;
}
const recyclableInstance: Instance = workInProgress.stateNode;
const currentHostContext = getHostContext();
let updatePayload = null;
if (oldProps !== newProps) {
updatePayload = prepareUpdate(
recyclableInstance,type,oldProps,newProps,rootContainerInstance,currentHostContext);
}
if (childrenUnchanged && updatePayload === null) {
workInProgress.stateNode = currentInstance;
return;
}
let newInstance = cloneInstance(
currentInstance,updatePayload,type,oldProps,newProps,
workInProgress,childrenUnchanged,recyclableInstance,
);
if (
finalizeInitialChildren(newInstance,type,newProps,rootContainerInstance,
currentHostContext)
) {
markUpdate(workInProgress);
}
workInProgress.stateNode = newInstance;
if (childrenUnchanged) { markUpdate(workInProgress); }
else { appendAllChildren(newInstance, workInProgress, false, false); }
};
}
HostRoot
在workLoop中,hostRoot是最先开始beginWork,是最后一个completeWork。hostRoot的completeWork标志着React Tree树的完成。
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case HostRoot: {
popHostContainer(workInProgress);
popTopLevelLegacyContextObject(workInProgress);
const fiberRoot = (workInProgress.stateNode: FiberRoot);
if (fiberRoot.pendingContext) {
fiberRoot.context = fiberRoot.pendingContext;
fiberRoot.pendingContext = null;
}
if (current === null || current.child === null) {
popHydrationState(workInProgress);
workInProgress.effectTag &= ~Placement;
}
updateHostContainer(workInProgress);
break;
}
}
若没有effect链表,标志着没有更新,则不做处理。updateHostContainer 中的finalizeContainerChildren 和hostComponent中的 finalizeInitialChildren 不是同一个方法。更新再说。
if(supportsMutation){
updateHostContainer = function(workInProgress: Fiber) {
// Noop
};
}else if (supportsPersistence){
updateHostContainer = function(workInProgress: Fiber) {
const portalOrRoot: {
containerInfo: Container,
pendingChildren: ChildSet,
} = workInProgress.stateNode;
const childrenUnchanged = workInProgress.firstEffect === null;
if (childrenUnchanged) {
// No changes, just reuse the existing instance.
} else {
const container = portalOrRoot.containerInfo;
let newChildSet = createContainerChildSet(container);
// 如果子元素发生了变化,我们必须将它们全部添加到集合中
appendAllChildrenToContainer(newChildSet, workInProgress, false, false);
portalOrRoot.pendingChildren = newChildSet;
// Schedule an update on the container to swap out the container.
markUpdate(workInProgress);
finalizeContainerChildren(container, newChildSet);
}
};
}
Effect
在 completeWork 的同时,也会生成链表。使用链表可以更高效更新fiber。react Tree 是保存在内存中的dom tree数据,react tree上包含链表数据,react Tree数据会在commit阶段使用。链表请参考此处。