若排除其它的操作项,每一次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: {// 代码见下面的HostRootbreak;}case HostComponent: {// 代码见下面的HostComponentbreak;}case HostText: {// 代码见下面的HostTextbreak;}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 fiberbreak;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-effectupdateHostText(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') {//绑定一个 functiontrapClickOnNonInteractiveElement(((domElement: any): HTMLElement));}break;}}// packages\react-dom\src\client\ReactDOMComponent.jsfunction setInitialDOMProperties(tag,domElement, rootContainerElement,nextProps,isCustomComponentTag){for (const propKey in nextProps) {//循环添加属性if (!nextProps.hasOwnProperty(propKey)) {continue;}const nextProp = nextProps[propKey];if (propKey === STYLE) {//stylesetValueForStyles(domElement, nextProp);} else if (propKey === DANGEROUSLY_SET_INNER_HTML){//htmlconst nextHtml = nextProp ? nextProp[HTML] : undefined;if (nextHtml != null) {setInnerHTML(domElement, nextHtml);}} else if (propKey === CHILDREN) {//childrenif (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阶段使用。链表请参考此处。
