img节点
completeUnitOfWork


function completeUnitOfWork(unitOfWork) {// Attempt to complete the current unit of work, then move to the next// sibling. If there are no more siblings, return to the parent fiber.var completedWork = unitOfWork;do {// The current, flushed, state of this fiber is the alternate. Ideally// nothing should rely on this, but relying on it here means that we don't// need an additional field on the work in progress.var current = completedWork.alternate;var returnFiber = completedWork.return; // Check if the work completed or if something threw.if ((completedWork.flags & Incomplete) === NoFlags) {setCurrentFiber(completedWork);var next = void 0;if ( (completedWork.mode & ProfileMode) === NoMode) {next = completeWork(current, completedWork, subtreeRenderLanes);} else {startProfilerTimer(completedWork);next = completeWork(current, completedWork, subtreeRenderLanes); // Update render duration assuming we didn't error.stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);}resetCurrentFiber();//img的子节点没null跳过if (next !== null) {// Completing this fiber spawned new work. Work on that next.workInProgress = next;return;}} else {// This fiber did not complete because something threw. Pop values off// the stack without entering the complete phase. If this is a boundary,// capture values if possible.var _next = unwindWork(current, completedWork); // Because this fiber did not complete, don't reset its lanes.if (_next !== null) {// If completing this work spawned new work, do that next. We'll come// back here again.// Since we're restarting, remove anything that is not a host effect// from the effect tag._next.flags &= HostEffectMask;workInProgress = _next;return;}if ( (completedWork.mode & ProfileMode) !== NoMode) {// Record the render duration for the fiber that errored.stopProfilerTimerIfRunningAndRecordDelta(completedWork, false); // Include the time spent working on failed children before continuing.var actualDuration = completedWork.actualDuration;var child = completedWork.child;while (child !== null) {actualDuration += child.actualDuration;child = child.sibling;}completedWork.actualDuration = actualDuration;}if (returnFiber !== null) {// Mark the parent fiber as incomplete and clear its subtree flags.returnFiber.flags |= Incomplete;returnFiber.subtreeFlags = NoFlags;returnFiber.deletions = null;} else {// We've unwound all the way to the root.workInProgressRootExitStatus = RootDidNotComplete;workInProgress = null;return;}}//没有子节点,进入兄弟节点var siblingFiber = completedWork.sibling;if (siblingFiber !== null) {// If there is more work to do in this returnFiber, do that next.workInProgress = siblingFiber;return;} // Otherwise, return to the parentcompletedWork = returnFiber; // Update the next thing we're working on in case something throws.workInProgress = completedWork;//循环completedWork,直到root} while (completedWork !== null); // We've reached the root.if (workInProgressRootExitStatus === RootInProgress) {workInProgressRootExitStatus = RootCompleted;}}
第一个进入completeWork的是img节点,且tag为5,根据fiber节点的tag进入不同的case,进入HostComponent
首先判断current是否存在,首屏渲染中,current为null
_wasHydrated与srr相关,当前为false
completeWork
function completeWork(current, workInProgress, renderLanes) {var newProps = workInProgress.pendingProps; // Note: This intentionally doesn't check if we're hydrating because comparing// to the current tree provider fiber is just as fast and less error-prone.// Ideally we would have a special version of the work loop only// for hydration.popTreeContext(workInProgress);//当前img节点的tag是5,进入hostComponentswitch (workInProgress.tag) {case IndeterminateComponent://对于FunctionComponent等其他组件没有completeWorkcase LazyComponent:case SimpleMemoComponent:case FunctionComponent:case ForwardRef:case Fragment:case Mode:case Profiler:case ContextConsumer:case MemoComponent:bubbleProperties(workInProgress);return null;...case HostComponent:{popHostContext(workInProgress);var rootContainerInstance = getRootHostContainer();var type = workInProgress.type;//判断current是否存在,首屏渲染时,current不存在if (current !== null && workInProgress.stateNode != null) {updateHostComponent$1(current, workInProgress, type, newProps, rootContainerInstance);if (current.ref !== workInProgress.ref) {markRef$1(workInProgress);}} else {if (!newProps) { //当前组件获得的属性,例如img的src、className等内容if (workInProgress.stateNode === null) {throw new Error('We must have new props for new mounts. This error is likely ' + 'caused by a bug in React. Please file an issue.');} // This can happen when we abort work.bubbleProperties(workInProgress);return null;}var currentHostContext = getHostContext(); // TODO: Move createInstance to beginWork and keep it on a context// "stack" as the parent. Then append children as we go in beginWork// or completeWork depending on whether we want to add them top->down or// bottom->up. Top->down is faster in IE11.//与srr相关,当前为falsevar _wasHydrated = popHydrationState(workInProgress);if (_wasHydrated) {// TODO: Move this and createInstance step into the beginPhase// to consolidate.if (prepareToHydrateHostInstance(workInProgress, rootContainerInstance, currentHostContext)) {// If changes to the hydrated node need to be applied at the// commit-phase we mark this as such.markUpdate(workInProgress);}} else {//进入createInstance 为HostComponent节点创建对应的dom节点var instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress);//将创建好的dom节点,插入到已经创建好的dom树中appendAllChildren(instance, workInProgress, false, false);workInProgress.stateNode = instance; // Certain renderers require commit-time effects for initial mount.// (eg DOM renderer supports auto-focus for certain elements).// Make sure such renderers get scheduled for later work.if (finalizeInitialChildren(instance, type, newProps, rootContainerInstance)) {markUpdate(workInProgress);}}if (workInProgress.ref !== null) {// If there is a ref on a host node we need to schedule a callbackmarkRef$1(workInProgress);}}bubbleProperties(workInProgress);return null;}...throw new Error("Unknown unit of work tag (" + workInProgress.tag + "). This error is likely caused by a bug in " + 'React. Please file an issue.');}
createInstance -创建对应的dom节点
为hostComponent创建对应的dom节点
createElement-createInstance方法中的createElement生成img的dom元素

function createInstance(type, props, rootContainerInstance, hostContext, internalInstanceHandle) {var parentNamespace = void 0...//生成当前dom节点的dom元素var domElement = createElement(type, props, rootContainerInstance, parentNamespace)precacheFiberNode(internalInstanceHandle, domElement)updateFiberProps(domElement, props)return domElement}
appendAllChildren-将createInstance创建的dom节点插入到已经创建好的dom数中
由于img是第一个dom节点,会跳过此次操作。
{// // 将子孙DOM节点插入刚生成的DOM节点中appendAllChildren = function (parent, workInProgress, needsVisibilityToggle, isHidden) {// We only have the top Fiber that was created but we need recurse down its// children to find all the terminal nodes.var node = workInProgress.child;while (node !== null) {if (node.tag === HostComponent || node.tag === HostText) {appendInitialChild(parent, node.stateNode);} else if (node.tag === HostPortal) ; else if (node.child !== null) {node.child.return = node;node = node.child;continue;}if (node === workInProgress) {return;}while (node.sibling === null) {if (node.return === null || node.return === workInProgress) {return;}node = node.return;}node.sibling.return = node.return;node = node.sibling;}};
workInProgress.stateNode=instance
将imgdom节点赋值给workInProgress.stateNode上面
finalizeInitialChildren-为dom节点设置一些属性

function finalizeInitialChildren(domElement, type, props, rootContainerInstance, hostContext) {//设置初始化属性的操作setInitialProperties(domElement, type, props, rootContainerInstance);switch (type) {case 'button':case 'input':case 'select':case 'textarea':return !!props.autoFocus;case 'img':return true;default:return false;}}
setInitialProperties-设置初始化属性
function setInitialProperties(domElement, tag, rawProps, rootContainerElement) {//是否是自定义的标签var isCustomComponentTag = isCustomComponent(tag, rawProps);{validatePropertiesInDevelopment(tag, rawProps);} // TODO: Make sure that we check isMounted before firing any of these events.var props;// 根据tag进入不同的标签switch (tag) {...case 'img':case 'image':case 'link':// We listen to these events in case to ensure emulated bubble// listeners still fire for error and load events.listenToNonDelegatedEvent('error', domElement);listenToNonDelegatedEvent('load', domElement);props = rawProps;...default:props = rawProps;}//判断props是否合法assertValidProps(tag, props);//初始化dom属性的操作setInitialDOMProperties(tag, domElement, rootContainerElement, props, isCustomComponentTag);switch (tag) {case 'input':// TODO:确保我们检查这是否仍处于卸载状态或进行任何清理// up necessary since we never stop tracking anymore.track(domElement);postMountWrapper(domElement, rawProps, false);break;case 'textarea':// TODO: Make sure we check if this is still unmounted or do any clean// up necessary since we never stop tracking anymore.track(domElement);postMountWrapper$3(domElement);break;case 'option':postMountWrapper$1(domElement, rawProps);break;case 'select':postMountWrapper$2(domElement, rawProps);break;default:if (typeof props.onClick === 'function') {// TODO: This cast may not be sound for SVG, MathML or custom elements.trapClickOnNonInteractiveElement(domElement);}break;}} // Calculate the diff between the two objects.
setInitialDOMProperties-初始化dom元素的值

function setInitialDOMProperties(tag, domElement, rootContainerElement, nextProps, isCustomComponentTag) {for (var propKey in nextProps) { //遍历nextProps,通过nextProps给相关dom属性赋值if (!nextProps.hasOwnProperty(propKey)) {continue;}var nextProp = nextProps[propKey];if (propKey === STYLE) {//STYLE =>'style'属性{if (nextProp) {// Freeze the next style object so that we can assume it won't be// mutated. We have already warned for this in the past.Object.freeze(nextProp);}} // Relies on `updateStylesByID` not mutating `styleUpdates`.setValueForStyles(domElement, nextProp);} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {var nextHtml = nextProp ? nextProp[HTML$1] : undefined;if (nextHtml != null) {setInnerHTML(domElement, nextHtml);}} else if (propKey === CHILDREN) {if (typeof nextProp === 'string') {// Avoid setting initial textContent when the text is empty. In IE11 setting// textContent on a <textarea> will cause the placeholder to not// show within the <textarea> until it has been focused and blurred again.// https://github.com/facebook/react/issues/6731#issuecomment-254874553var 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 (registrationNameDependencies.hasOwnProperty(propKey)) {if (nextProp != null) {if ( typeof nextProp !== 'function') {warnForInvalidEventListener(propKey, nextProp);}if (propKey === 'onScroll') {listenToNonDelegatedEvent('scroll', domElement);}}} else if (nextProp != null) {setValueForProperty(domElement, propKey, nextProp, isCustomComponentTag);}}}
setValueForProperty-使用setAttribute给dom节点的属性赋值

function setValueForProperty(node, name, value, isCustomComponentTag) {..if (isCustomComponentTag || propertyInfo === null) {if (isAttributeNameSafe(name)) {var _attributeName = name;if (value === null) {node.removeAttribute(_attributeName);} else {{checkAttributeStringCoercion(value, name);}node.setAttribute(_attributeName, '' + value);}}return;}...var attributeName = propertyInfo.attributeName,attributeNamespace = propertyInfo.attributeNamespace;if (value === null) {node.removeAttribute(attributeName);} else {var _type = propertyInfo.type;var attributeValue;if (_type === BOOLEAN || _type === OVERLOADED_BOOLEAN && value === true) {// If attribute type is boolean, we know for sure it won't be an execution sink// and we won't require Trusted Type here.attributeValue = '';} else {// `setAttribute` with objects becomes only `[object]` in IE8/9,// ('' + value) makes it output the correct toString()-value.{{checkAttributeStringCoercion(value, attributeName);}attributeValue = '' + value;}if (propertyInfo.sanitizeURL) {sanitizeURL(attributeValue.toString());}}if (attributeNamespace) {node.setAttributeNS(attributeNamespace, attributeName, attributeValue);} else {//给dom属性赋值node.setAttribute(attributeName, attributeValue);}}}
markUpdate-更新
最终,完成一个fiber节点的completeWork完成。
下一个进入completeWork的是edit文本-HostText
为HostText-创建文本节点
function completeWork(current, workInProgress, renderLanes) {var newProps = workInProgress.pendingProps; // Note: This intentionally doesn't check if we're hydrating because comparing// to the current tree provider fiber is just as fast and less error-prone.// Ideally we would have a special version of the work loop only// for hydration.popTreeContext(workInProgress);//当前Edit节点的tag是6,进入HostTextswitch (workInProgress.tag) {...case HostText:{var newText = newProps;//mount阶段 current为nullif (current && workInProgress.stateNode != null) {var oldText = current.memoizedProps; // If we have an alternate, that means this is an update and we need// to schedule a side-effect to do the updates.updateHostText$1(current, workInProgress, oldText, newText);} else {if (typeof newText !== 'string') {if (workInProgress.stateNode === null) {throw new Error('We must have new props for new mounts. This error is likely ' + 'caused by a bug in React. Please file an issue.');} // This can happen when we abort work.}//获取项目根据节点,当前为#rootvar _rootContainerInstance = getRootHostContainer();//获取当前上下文var _currentHostContext = getHostContext();var _wasHydrated2 = popHydrationState(workInProgress);if (_wasHydrated2) {if (prepareToHydrateHostTextInstance(workInProgress)) {markUpdate(workInProgress);}} else {//因为是文本节点-创建dom节点workInProgress.stateNode = createTextInstance(newText, _rootContainerInstance, _currentHostContext, workInProgress);}}bubbleProperties(workInProgress);return null;}...}
createTextInstance
function createTextInstance(text, rootContainerInstance, hostContext, internalInstanceHandle) {{var hostContextDev = hostContext;//检查dom元素是否有效validateDOMNesting(null, text, hostContextDev.ancestorInfo);}//生成文本节点var textNode = createTextNode(text, rootContainerInstance);precacheFiberNode(internalInstanceHandle, textNode);return textNode;}
createTextNode-为文本节点设置属性

precacheFiberNode-生成一个Fiber节点,并当completeWorkp标签的appendAllChildren追加。
function precacheFiberNode(hostInst, node) {node[internalInstanceKey] = hostInst;}
最终,完成一个文本fiber节点的completeWork完成。
下一个进入文本节点是src/App.js
下一个是code
下一个是and save to reload
下一个是p,
下一个是 Learn React
下一个是a
下一个是header
下一个是div className=’App’
下一个是div className=’root’
最终生成一个Fiber树
由于completeWork属于“归”阶段调用的函数,每次调用appendAllChildren时都会将已生成的子孙DOM节点插入当前生成的DOM节点下。那么当“归”到rootFiber时,我们已经有一个构建好的离屏DOM树。
首屏渲染,dom节点如何挂载到页面中?

首屏渲染时,只有一个fiber节点,就是div #root根fiber节点
function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {if (current === null) {workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);} else {workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes);}}
function reconcileChildFibers(returnFiber, currentFirstChild, newChild, lanes) {var isUnkeyedTopLevelFragment = typeof newChild === 'object' && newChild !== null && newChild.type === REACT_FRAGMENT_TYPE && newChild.key === null;if (isUnkeyedTopLevelFragment) {newChild = newChild.props.children;} // Handle object typesif (typeof newChild === 'object' && newChild !== null) {switch (newChild.$$typeof) {case REACT_ELEMENT_TYPE:return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild, lanes));...return reconcileChildFibers(returnFiber, currentFirstChild, init(payload), lanes);}...return deleteRemainingChildren(returnFiber, currentFirstChild);}return reconcileChildFibers;}
function placeSingleChild(newFiber) {// This is simpler for the single child case. We only need to do a// placement for inserting new children.if (shouldTrackSideEffects && newFiber.alternate === null) {newFiber.flags |= Placement;}return
首次渲染阶段,fiber.effctTag为Placement。
