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 parent
completedWork = 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,进入hostComponent
switch (workInProgress.tag) {
case IndeterminateComponent:
//对于FunctionComponent等其他组件没有completeWork
case 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相关,当前为false
var _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 callback
markRef$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-254874553
var 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,进入HostText
switch (workInProgress.tag) {
...
case HostText:
{
var newText = newProps;
//mount阶段 current为null
if (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.
}
//获取项目根据节点,当前为#root
var _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 types
if (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。