function performUnitOfWork(workInProgress: Fiber): Fiber | null {
const current = workInProgress.alternate;
// See if beginning this work spawns more work.
startWorkTimer(workInProgress);
let next;
if (enableProfilerTimer) {
if (workInProgress.mode & ProfileMode) {
startProfilerTimer(workInProgress);
}
// 执行对整个树每个节点进行更新的操作,赋值 next 为更新完后的下一个节点
next = beginWork(current, workInProgress, nextRenderExpirationTime);
// 将最新的 props 赋值给目前正在用的 props
workInProgress.memoizedProps = workInProgress.pendingProps;
if (workInProgress.mode & ProfileMode) {
// Record the render duration assuming we didn't bailout (or error).
stopProfilerTimerIfRunningAndRecordDelta(workInProgress, true);
}
} else {
next = beginWork(current, workInProgress, nextRenderExpirationTime);
workInProgress.memoizedProps = workInProgress.pendingProps;
}
// 更新到最后的节点,叶子节点
if (next === null) {
// If this doesn't spawn new work, complete the current work.
// 有兄弟节点返回兄弟节点,否则返回null,往上遍历
next = completeUnitOfWork(workInProgress);
}
ReactCurrentOwner.current = null;
return next;
}
function forceUnmountCurrentAndReconcile(
current: Fiber,
workInProgress: Fiber,
nextChildren: any,
renderExpirationTime: ExpirationTime,
) {
// This function is fork of reconcileChildren. It's used in cases where we
// want to reconcile without matching against the existing set. This has the
// effect of all current children being unmounted; even if the type and key
// are the same, the old child is unmounted and a new child is created.
//
// To do this, we're going to go through the reconcile algorithm twice. In
// the first pass, we schedule a deletion for all the current children by
// passing null.
// nextChildren 为 null, 强制把目前子树节点都删除,渲染出没有子树的 classComponent
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
null,
renderExpirationTime,
);
// In the second pass, we mount the new children. The trick here is that we
// pass null in place of where we usually pass the current child set. This has
// the effect of remounting all children regardless of whether their their
// identity matches.
// 老的 children 为 null,清空老的,直接渲染新的 children ,跳过对比 key 的调和过程,提高渲染效率
// nextChildren 已经是根据从错误的 update 里计算出来的state渲染的 children 了
workInProgress.child = reconcileChildFibers(
workInProgress,
null,
nextChildren,
renderExpirationTime,
);
}
function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
// 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.
while (true) {
// 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.
const current = workInProgress.alternate;
// 父节点
const returnFiber = workInProgress.return;
// 兄弟节点
const siblingFiber = workInProgress.sibling;
// Incomplete 为这个节点出现错误并捕获的 effect
// 逻辑与操作判断 workInProgress.effectTag 是否为 Incomplete
// Incomplete & Incomplete => Incomplete
// 非 Incomplete & Incomplete => NoEffect 即没错的情况
if ((workInProgress.effectTag & Incomplete) === NoEffect) {
// This fiber completed.
if (enableProfilerTimer) {
if (workInProgress.mode & ProfileMode) {
startProfilerTimer(workInProgress);
}
nextUnitOfWork = completeWork(
current,
workInProgress,
nextRenderExpirationTime,
);
if (workInProgress.mode & ProfileMode) {
// Update render duration assuming we didn't error.
stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
}
} else {
nextUnitOfWork = completeWork(
current,
workInProgress,
nextRenderExpirationTime,
);
}
// 更新都是从 root 节点开始,需要同步子树中更新优先级最高的 expirationTime 到 root 上
// 完成一次节点更新后,需要更新 childExpirationTime
// 重置 childExpirationTime (子节点当中更新优先级最高的对应的 expirationTime )
stopWorkTimer(workInProgress);
resetChildExpirationTime(workInProgress, nextRenderExpirationTime);
if (
returnFiber !== null &&
// Do not append effects to parents if a sibling failed to complete
// 没有错误才会更新, 有错误的不会渲染子节点的
(returnFiber.effectTag & Incomplete) === NoEffect
) {
// Append all the effects of the subtree and this fiber onto the effect
// list of the parent. The completion order of the children affects the
// side-effect order.
// 处理父节点的 effect
// 真正要做的是把当前节点的 firstEffect 到 lastEffect (即当前节点的子节点的 effect ) 单向链表挂载到它的父节点的同样的位置
// 表示 returnFiber 还未记录任何有副作用(effect)的子节点
if (returnFiber.firstEffect === null) {
// 当前节点的 firstEffect 赋值给 returnFiber
returnFiber.firstEffect = workInProgress.firstEffect;
}
if (workInProgress.lastEffect !== null) {
// 如果 returnFiber 已经有记录过别的 side effect 节点,当前节点的 firstEffect 则挂载在单向链表的最后
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
}
// 更新 lastEffect
returnFiber.lastEffect = workInProgress.lastEffect;
}
// 处理当前节点的 effect
// 当前节点也可能有 effect
const effectTag = workInProgress.effectTag;
// Skip both NoWork and PerformedWork tags when creating the effect list.
// PerformedWork effect is read by React DevTools but shouldn't be committed.
// 如果当前节点有 effect 且大于 1, PerformeWork 值为 1 给 devtool 用的,则将当前节点挂载到父节点的 side effect 链的最后一个
if (effectTag > PerformedWork) {
// 如果 returnFiber 有记录过别的 effect
if (returnFiber.lastEffect !== null) {
// 更新最后一个的 next
returnFiber.lastEffect.nextEffect = workInProgress;
} else {
// 否则为第一个 effect
returnFiber.firstEffect = workInProgress;
}
// 更新 lastEffect
returnFiber.lastEffect = workInProgress;
}
// 一层一层往上,最终在 RootFiber 上赋值的 firstEffect 到 lastEffect 这个单向链表结构记录了整个应用的当中所有需要更新的 dom节点/组件对应的 fiber 对象 ,以最小程度更新应用
}
// 如果有兄弟节点则返回兄弟节点,跳出这个循环,继续 beginWork
if (siblingFiber !== null) {
// If there is more work to do in this returnFiber, do that next.
return siblingFiber;
// 如果没有兄弟节点,循环找父节点的兄弟节点
} else if (returnFiber !== null) {
// If there's no more work in this returnFiber. Complete the returnFiber.
workInProgress = returnFiber;
continue;
} else {
// 如果都没有,则到达 rootFiber 顶点了,更新阶段完成,进入 commitRoot 提交阶段
// We've reached the root.
return null;
}
} else {
// Incomplete 抛出过错误的情况, workInProgress 为报错的那个组件, 并不一定是能处理错误的那个组件,可能不是 classComponent 或者 没有错误处理的能力
// 可以处理错误的组件才会标记 shouldCapture,可能是父组件
// 即 unwindWork 执行完后 next 可能为 null 的情况
if (workInProgress.mode & ProfileMode) {
// Record the render duration for the fiber that errored.
stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
}
// 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.
// workInProgress 的 effect 如果为 shouldCapture ,那么返回 workInProgress ,并且将 shouldCapture 改成 DidCapture
// 否则返回 null
const next = unwindWork(workInProgress, nextRenderExpirationTime);
// Because this fiber did not complete, don't reset its expiration time.
if (workInProgress.effectTag & DidCapture) {
// Restarting an error boundary
stopFailedWorkTimer(workInProgress);
} else {
stopWorkTimer(workInProgress);
}
// next 不为 null 即 workInProgress 是有 DidCapture 的 effectTag
if (next !== null) {
stopWorkTimer(workInProgress);
if (enableProfilerTimer) {
// Include the time spent working on failed children before continuing.
if (next.mode & ProfileMode) {
let actualDuration = next.actualDuration;
let child = next.child;
while (child !== null) {
actualDuration += child.actualDuration;
child = child.sibling;
}
next.actualDuration = actualDuration;
}
}
// 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.
// 将 effectTag 变成只有 HostEffectMask 上所具有的 effect
// shouldCapture、Incomplete 会被去掉
next.effectTag &= HostEffectMask;
// 返回的是能处理错误的那个节点,再继续 beginWork,此时会根据是否有 DidCapture 在调和子组件中做不同操作。
return next;
}
if (returnFiber !== null) {
// Mark the parent fiber as incomplete and clear its effect list.
// 清空父链的 effect 链
returnFiber.firstEffect = returnFiber.lastEffect = null;
// 报错组件没有错误处理能力时,需要在父组件上增加 Incomplete,
// 即子树中的组件如果报错,那对于父链上所有组件的 completeUnitOfWork 都会执行 unwindWork
returnFiber.effectTag |= Incomplete;
}
if (siblingFiber !== null) {
// If there is more work to do in this returnFiber, do that next.
return siblingFiber;
} else if (returnFiber !== null) {
// If there's no more work in this returnFiber. Complete the returnFiber.
workInProgress = returnFiber;
continue;
} else {
return null;
}
}
}
return null;
}
appendAllChildren = function(
parent: Instance, // 创建的 dom
workInProgress: Fiber,
needsVisibilityToggle: boolean,
isHidden: boolean,
) {
// We only have the top Fiber that was created but we need recurse down its
// children to find all the terminal nodes.
// 当前节点的子节点
let node = workInProgress.child;
while (node !== null) {
if (node.tag === HostComponent || node.tag === HostText) {
// 子节点是 原生 dom 节点 或者纯文本节点
// 直接调用原生 dom 的 appendChild
appendInitialChild(parent, node.stateNode);
} else if (node.tag === HostPortal) {
// If we have a portal child, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
// the portal directly.
} else if (node.child !== null) {
// 子节点不是 dom 标签或者纯文本节点, 且 child.child 不为null
node.child.return = node;
// 重新赋值 node 为 node.child,并跳过此次循环,进入下一次循环,一直往下找直到找到 dom 节点 或者 纯文本节点
node = node.child;
continue;
}
if (node === workInProgress) {
return;
}
// 这个 while 循环目的是找到有兄弟节点的父节点,如果父节点有兄弟节点,则跳出这个小循环,会去遍历其兄弟节点
// 如果没有兄弟节点,继续往上找最终会回到 node.return === workInProgress 跳出循环
while (node.sibling === null) {
if (node.return === null || node.return === workInProgress) {
return;
}
node = node.return;
}
// 将兄弟节点的父节点都指向同一个
node.sibling.return = node.return;
// 有兄弟节点继续遍历
node = node.sibling;
}
};
function finalizeInitialChildren(
domElement: Instance,
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: HostContext,
): boolean {
setInitialProperties(domElement, type, props, rootContainerInstance);
// 节点需要 autofocus 时才 mark update
return shouldAutoFocusHostComponent(type, props);
}
// setInitialProperties
function setInitialProperties(
domElement: Element,
tag: string,
rawProps: Object,
rootContainerElement: Element | Document,
): void {
const isCustomComponentTag = isCustomComponent(tag, rawProps);
if (__DEV__) {}
// TODO: Make sure that we check isMounted before firing any of these events.
// trapBubbledEvent 绑定不同事件
let props: Object;
switch (tag) {
case 'iframe':
case 'object':
trapBubbledEvent(TOP_LOAD, domElement);
props = rawProps;
break;
case 'video':
case 'audio':
// Create listener for each media event
for (let i = 0; i < mediaEventTypes.length; i++) {
trapBubbledEvent(mediaEventTypes[i], domElement);
}
props = rawProps;
break;
case 'source':
trapBubbledEvent(TOP_ERROR, domElement);
props = rawProps;
break;
case 'img':
case 'image':
case 'link':
trapBubbledEvent(TOP_ERROR, domElement);
trapBubbledEvent(TOP_LOAD, domElement);
props = rawProps;
break;
case 'form':
trapBubbledEvent(TOP_RESET, domElement);
trapBubbledEvent(TOP_SUBMIT, domElement);
props = rawProps;
break;
case 'details':
trapBubbledEvent(TOP_TOGGLE, domElement);
props = rawProps;
break;
// input option select textarea 为可交互输入的组件
case 'input':
ReactDOMInput.initWrapperState(domElement, rawProps);
props = ReactDOMInput.getHostProps(domElement, rawProps);
trapBubbledEvent(TOP_INVALID, domElement);
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange');
break;
case 'option':
ReactDOMOption.validateProps(domElement, rawProps);
props = ReactDOMOption.getHostProps(domElement, rawProps);
break;
case 'select':
ReactDOMSelect.initWrapperState(domElement, rawProps);
props = ReactDOMSelect.getHostProps(domElement, rawProps);
trapBubbledEvent(TOP_INVALID, domElement);
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange');
break;
case 'textarea':
ReactDOMTextarea.initWrapperState(domElement, rawProps);
props = ReactDOMTextarea.getHostProps(domElement, rawProps);
trapBubbledEvent(TOP_INVALID, domElement);
// For controlled components we always need to ensure we're listening
// to onChange. Even if there is no listener.
ensureListeningTo(rootContainerElement, 'onChange');
break;
default:
props = rawProps;
}
// 常用 props 错误提醒
assertValidProps(tag, props);
// 初始化 DOM 节点属性
setInitialDOMProperties(
tag,
domElement,
rootContainerElement,
props,
isCustomComponentTag,
);
// ...
}
// setInitialDOMProperties
function setInitialDOMProperties(
tag: string,
domElement: Element,
rootContainerElement: Element | Document,
nextProps: Object,
isCustomComponentTag: boolean,
): void {
for (const propKey in nextProps) {
if (!nextProps.hasOwnProperty(propKey)) {
continue;
}
const nextProp = nextProps[propKey];
if (propKey === STYLE) {
// style 属性
if (__DEV__) {
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`.
// 设置样式,对于 width 类属性,自动加 px
CSSPropertyOperations.setValueForStyles(domElement, nextProp);
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
// 文本属性,直接用原生 node.innerHTML = html 设置
const nextHtml = nextProp ? nextProp[HTML] : undefined;
if (nextHtml != null) {
setInnerHTML(domElement, nextHtml);
}
} else if (propKey === CHILDREN) {
// 子节点,这里只会是文本或数字了,dom 节点或者组件已经在 appendAllChildren 操作完了
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
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
) {
// Noop
} else if (propKey === AUTOFOCUS) {
// We polyfill it separately on the client during commit.
// We could have excluded it in the property list instead of
// adding a special case here, but then it wouldn't be emitted
// on server rendering (but we *do* want to emit it in SSR).
} else if (registrationNameModules.hasOwnProperty(propKey)) {
// 事件相关
if (nextProp != null) {
if (__DEV__ && typeof nextProp !== 'function') {
warnForInvalidEventListener(propKey, nextProp);
}
// 事件监听
ensureListeningTo(rootContainerElement, propKey);
}
} else if (nextProp != null) {
// 最终 node.setAttribute 设置属性,或者 removeAttribute 删除不必要的属性
DOMPropertyOperations.setValueForProperty(
domElement,
propKey,
nextProp,
isCustomComponentTag,
);
}
}
}
updateHostComponent 更新渲染
非首次渲染更新,需要对新老 props 里对应 dom 的 property 的对比是否有变化来进行不同的操作
updateHostComponent = function(
current: Fiber,
workInProgress: Fiber,
type: Type,
newProps: Props,
rootContainerInstance: Container,
) {
// If we have an alternate, that means this is an update and we need to
// schedule a side-effect to do the updates.
const oldProps = current.memoizedProps;
// 新老 props 相同,直接 return
if (oldProps === newProps) {
return;
}
// 当前节点对应的 DOM 节点
const instance: Instance = workInProgress.stateNode;
const currentHostContext = getHostContext();
// TODO: Experiencing an error where oldProps is null. Suggests a host
// component is hitting the resume path. Figure out why. Possibly
// related to `hidden`.
// updatePayload 为调用 diffProperties 返回的结果
const updatePayload = prepareUpdate(
instance,
type,
oldProps,
newProps,
rootContainerInstance,
currentHostContext,
);
// TODO: Type this specific to this type of component.
workInProgress.updateQueue = (updatePayload: any);
// If the update payload indicates that there is a change or if there
// is a new ref we mark this as an update. All the work is done in commitWork.
// 如果有 updatePayload 则要标记更新,对于 input,option, select, textarea 默认为空数组,即便是 props 没有更新的内容,仍旧需要标记 update
if (updatePayload) {
markUpdate(workInProgress);
}
};
function diffProperties(
domElement: Element,
tag: string,
lastRawProps: Object,
nextRawProps: Object,
rootContainerElement: Element | Document,
): null | Array<mixed> {
if (__DEV__) {
validatePropertiesInDevelopment(tag, nextRawProps);
}
let updatePayload: null | Array<any> = null;
let lastProps: Object;
let nextProps: Object;
// 特殊标签
switch (tag) {
case 'input':
lastProps = ReactDOMInput.getHostProps(domElement, lastRawProps);
nextProps = ReactDOMInput.getHostProps(domElement, nextRawProps);
updatePayload = [];
break;
case 'option':
lastProps = ReactDOMOption.getHostProps(domElement, lastRawProps);
nextProps = ReactDOMOption.getHostProps(domElement, nextRawProps);
updatePayload = [];
break;
case 'select':
lastProps = ReactDOMSelect.getHostProps(domElement, lastRawProps);
nextProps = ReactDOMSelect.getHostProps(domElement, nextRawProps);
updatePayload = [];
break;
case 'textarea':
lastProps = ReactDOMTextarea.getHostProps(domElement, lastRawProps);
nextProps = ReactDOMTextarea.getHostProps(domElement, nextRawProps);
updatePayload = [];
break;
default:
lastProps = lastRawProps;
nextProps = nextRawProps;
if (
typeof lastProps.onClick !== 'function' &&
typeof nextProps.onClick === 'function'
) {
// TODO: This cast may not be sound for SVG, MathML or custom elements.
trapClickOnNonInteractiveElement(((domElement: any): HTMLElement));
}
break;
}
// props 错误提醒
assertValidProps(tag, nextProps);
let propKey;
let styleName;
let styleUpdates = null;
// 循环老的 props,如果新 props 没有而老 props 有则要删除(style属性:将value置为 ’‘,其他属性置为 null)这个属性
for (propKey in lastProps) {
if (
// 新的有
nextProps.hasOwnProperty(propKey) ||
// 老的没有
!lastProps.hasOwnProperty(propKey) ||
// 老的为 null
lastProps[propKey] == null
) {
// 如果符合这个条件则跳过,否则是新的没有且老的有,那么就要删除
continue;
}
// 不符合以上条件则是删除操作
if (propKey === STYLE) {
const lastStyle = lastProps[propKey];
for (styleName in lastStyle) {
if (lastStyle.hasOwnProperty(styleName)) {
if (!styleUpdates) {
styleUpdates = {};
}
// 设置删除老的 style props
styleUpdates[styleName] = '';
}
}
} else if (propKey === DANGEROUSLY_SET_INNER_HTML || propKey === CHILDREN) {
// Noop. This is handled by the clear text mechanism.
} else if (
propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
propKey === SUPPRESS_HYDRATION_WARNING
) {
// Noop
} else if (propKey === AUTOFOCUS) {
// Noop. It doesn't work on updates anyway.
} else if (registrationNameModules.hasOwnProperty(propKey)) {
// This is a special case. If any listener updates we need to ensure
// that the "current" fiber pointer gets updated so we need a commit
// to update this element.
if (!updatePayload) {
updatePayload = [];
}
} else {
// For all other deleted properties we add it to the queue. We use
// the whitelist in the commit phase instead.
// 要删除的属性,则 value 都设置为 null
(updatePayload = updatePayload || []).push(propKey, null);
}
}
// 循环新的 props,如果新 props 有这个 key,且新老 props[key] 值不想等则将这个 key 标记更新( style属性则为直接设置 style[key] = newValue ),
for (propKey in nextProps) {
// newValue
const nextProp = nextProps[propKey];
// oldValue
const lastProp = lastProps != null ? lastProps[propKey] : undefined;
if (
// 自身没有这个 key
!nextProps.hasOwnProperty(propKey) ||
// 新老相等
nextProp === lastProp ||
// 均为 null
(nextProp == null && lastProp == null)
) {
// 符合以上条件则跳过, 否则就要加入 updatePayload 中
continue;
}
// 对 style 属性的操作
if (propKey === STYLE) {
if (__DEV__) {
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);
}
}
if (lastProp) {
// Unset styles on `lastProp` but not on `nextProp`.
for (styleName in lastProp) {
if (
// style 在老的 props 中,但不在新的 props 中,则属性对应的 value 设置为 ‘’(对应删除)
lastProp.hasOwnProperty(styleName) &&
(!nextProp || !nextProp.hasOwnProperty(styleName))
) {
if (!styleUpdates) {
styleUpdates = {};
}
styleUpdates[styleName] = '';
}
}
// Update styles that changed since `lastProp`.
for (styleName in nextProp) {
// 如果新props 有且新老prop 值不想等,赋值 styleUpdates[styleName] 为新的值
if (
nextProp.hasOwnProperty(styleName) &&
lastProp[styleName] !== nextProp[styleName]
) {
if (!styleUpdates) {
styleUpdates = {};
}
styleUpdates[styleName] = nextProp[styleName];
}
}
} else {
// Relies on `updateStylesByID` not mutating `styleUpdates`.
if (!styleUpdates) {
if (!updatePayload) {
updatePayload = [];
}
// 没有老 props 直接添加
updatePayload.push(propKey, styleUpdates);
}
styleUpdates = nextProp;
}
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
const nextHtml = nextProp ? nextProp[HTML] : undefined;
const lastHtml = lastProp ? lastProp[HTML] : undefined;
if (nextHtml != null) {
if (lastHtml !== nextHtml) {
(updatePayload = updatePayload || []).push(propKey, '' + nextHtml);
}
} else {
// TODO: It might be too late to clear this if we have children
// inserted already.
}
} else if (propKey === CHILDREN) {
// 子节点,文本类型的,不相同直接 push
if (
lastProp !== nextProp &&
(typeof nextProp === 'string' || typeof nextProp === 'number')
) {
(updatePayload = updatePayload || []).push(propKey, '' + nextProp);
}
} else if (
propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
propKey === SUPPRESS_HYDRATION_WARNING
) {
// Noop
} else if (registrationNameModules.hasOwnProperty(propKey)) {
if (nextProp != null) {
// We eagerly listen to this even though we haven't committed yet.
if (__DEV__ && typeof nextProp !== 'function') {
warnForInvalidEventListener(propKey, nextProp);
}
// 事件相关
ensureListeningTo(rootContainerElement, propKey);
}
if (!updatePayload && lastProp !== nextProp) {
// This is a special case. If any listener updates we need to ensure
// that the "current" props pointer gets updated so we need a commit
// to update this element.
updatePayload = [];
}
} else {
// For any other property we always add it to the queue and then we
// filter it out using the whitelist during the commit.
(updatePayload = updatePayload || []).push(propKey, nextProp);
}
}
if (styleUpdates) {
(updatePayload = updatePayload || []).push(STYLE, styleUpdates);
}
// updatePayload 的格式为 [k1, v1, k2, v2, k3, v3]
return updatePayload;
}
HostText 更新
case HostText: {
let newText = newProps;
// 更新渲染
if (current && workInProgress.stateNode != null) {
const 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.
// 新老text不同则标记更新
updateHostText(current, workInProgress, oldText, newText);
} else {
// 首次渲染
if (typeof newText !== 'string') {
invariant(
workInProgress.stateNode !== null,
'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.
}
const rootContainerInstance = getRootHostContainer();
const currentHostContext = getHostContext();
let wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
if (prepareToHydrateHostTextInstance(workInProgress)) {
markUpdate(workInProgress);
}
} else {
// 创建 textInstance
workInProgress.stateNode = createTextInstance(
newText,
rootContainerInstance,
currentHostContext,
workInProgress,
);
}
}
break;
}
// 判断不相同,则标记 update
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);
}
};
// createTextInstance 创建 textInstance
function createTextInstance(
text: string,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
): TextInstance {
if (__DEV__) {
const hostContextDev = ((hostContext: any): HostContextDev);
validateDOMNesting(null, text, hostContextDev.ancestorInfo);
}
// 调用 document.createTextNode
const textNode: TextInstance = createTextNode(text, rootContainerInstance);
// cache Fiber, 将 fiber 对象存储在 domElemeent 的 internalInstanceKey 属性上
precacheFiberNode(internalInstanceHandle, textNode);
return textNode;
}
// renderRoot
do {
try {
workLoop(isYieldy);
} catch (thrownValue) {
// 错误处理核心点:
// 给报错的节点增加 Incomplete 副作用,在 completeUnitOfWork 时会判断存在 Incomplete 则调用 unwindWork, 不存在则调用 completeWork
// 给父链上具有 error boundary 的节点增加副作用
// 创建错误相关的更新
// 非正常流程,不应该为 null
if (nextUnitOfWork === null) {
// This is a fatal error.
// 致命错误,直接中断渲染流程
didFatal = true;
// 设置 nextFlushedRoot.expirationTime = NoWork;
onUncaughtError(thrownValue);
} else {
if (__DEV__) {
// Reset global debug state
// We assume this is defined in DEV
(resetCurrentlyProcessingQueue: any)();
}
const failedUnitOfWork: Fiber = nextUnitOfWork;
if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
replayUnitOfWork(failedUnitOfWork, thrownValue, isYieldy);
}
// TODO: we already know this isn't true in some cases.
// At least this shows a nicer error message until we figure out the cause.
// https://github.com/facebook/react/issues/12449#issuecomment-386727431
invariant(
nextUnitOfWork !== null,
'Failed to replay rendering after an error. This ' +
'is likely caused by a bug in React. Please file an issue ' +
'with a reproducing case to help us find it.',
);
const sourceFiber: Fiber = nextUnitOfWork;
let returnFiber = sourceFiber.return;
// 在更新 rootFiber 时出现致命错误
if (returnFiber === null) {
// This is the root. The root could capture its own errors. However,
// we don't know if it errors before or after we pushed the host
// context. This information is needed to avoid a stack mismatch.
// Because we're not sure, treat this as a fatal error. We could track
// which phase it fails in, but doesn't seem worth it. At least
// for now.
didFatal = true;
onUncaughtError(thrownValue);
} else {
// 常规错误处理, 给报错节点增加 Incomplete 的 effect
throwException(
root,
returnFiber,
sourceFiber,
thrownValue,
nextRenderExpirationTime,
);
// 节点报错,则不会继续渲染它的子节点,直接 completeUnitOfWork ,直接走 unwindWork 流程
nextUnitOfWork = completeUnitOfWork(sourceFiber);
continue;
}
}
}
break;
} while (true);
回顾 performUnitOfWork
performUnitOfWork
遍历fiber
树的顺序completeUnitOfWork
beginWork
完成各个组件的更新,返回它的第一个child
,当返回null
时,执行completeUnitOfWork
beginWork
过程中, 在update
某个节点时如果报错或者throw promise
,会被catch
到,在throwException
中给节点增加Incomplete
的effect
;仍在renderRoot
的循环中,节点报错,则不会继续渲染它的子节点,直接completeUnitOfWork
,如果effect
为Incomplete
,直接走unwindWork
流程completeWork
之后首先执行resetChildExpirationTime
来重置childExpirationTime
completeWork
之后赋值的firstEffect
到lastEffect
链,在commit
阶段根据这个effect
链对每个节点执行操作unwindWork
: 如果effect
有shouldCapture
则将shouldCapture
改成DidCapture
,并返回节点,返回的是能处理错误的那个节点,跳出completeUnitOfWork
循环,再继续beginWork
,此时已经有一个错误的update
在updateQueue
中了,在processUpdateQueue
中会处理firstCapturedUpdate
,计算出有错误情况下的state
;否则返回null
finishClassComponent
中如果非首次渲染并且有DidCapture
则会调用forceUnmountCurrentAndReconcile
,进行两次reconcileChildFibers
throwException
中标记shouldCapture
的节点可能是报错节点的父节点,因此unwindWork
可能返回为null
rootFiber
completeUnitOfWork
返回节点跳出循环的情况:// You can change the rest (and add more). export const Placement = / / 0b00000000010; export const Update = / / 0b00000000100; export const PlacementAndUpdate = / / 0b00000000110; export const Deletion = / / 0b00000001000; export const ContentReset = / / 0b00000010000; export const Callback = / / 0b00000100000; export const DidCapture = / / 0b00001000000; export const Ref = / / 0b00010000000; export const Snapshot = / / 0b00100000000;
// Update & Callback & Ref & Snapshot export const LifecycleEffectMask = / / 0b00110100100;
// Union of all host effects // HostEffectMask 为以上 effect 的并集,Incomplete、ShouldCapture 为 HostEffectMask 的补集 export const HostEffectMask = / / 0b00111111111;
export const Incomplete = / / 0b01000000000; export const ShouldCapture = / / 0b10000000000;
createInstance 创建 DOM
workInProgress.type
创建dom
dom
对象上记录对应的fiber
和props
appendAllChildren 构建 dom 树
dom
树,从底往上append
子节点,只会append
所有子树里第一层dom/text
节点 及其对应的sibling
,不会append
child.child
中的dom/text
节点child
节点在执行completeUnitOfWork
会append
自己的child
和child.sibling
finalizeInitialChildren 初始化 DOM 节点属性,初始化事件体系
trapBubbledEvent
绑定不同事件setInitialDOMProperties
初始化dom
节点属性、和事件监听CSSPropertyOperations.setValueForStyles
初始化样式DOMPropertyOperations.setValueForProperty
初始化dom
属性,会对nextProp
做一些判断,要忽略的属性直接return
,要删除的属性,将通过node.removeAttribute
删除,否则通过node.setAttribute
添加updateHostComponent 更新渲染
props
里对应dom
的property
的对比是否有变化来进行不同的操作diffProperties
updatePayload
赋值给workInProgress.updateQueue
updatePayload
存在,则标记effect
为update
老 props
循环,如果新 props
没有而老 props
有则要删除新 props
循环,如果新 props
有这个key
,且新老 props[key]
值不想等则将这个key
标记更新updatePayload
结构为[k1, v1, k2, v2, k3, v3]
\HostText 更新
resetChildExpirationTime 重置 resetChildExpirationTime
childExpirationTime
childExpirationTime
是用来记录某一个节点子树当中优先级最高对应的那个expirationTime
root
节点开始,需要同步子树中更新优先级最高的expirationTime
到root
上completeWork
完成节点更新后执行,重置childExpirationTime
(子节点当中更新优先级最高的对应的expirationTime
)exprationTime
, 只能通过遍历所有的第一层的子节点的expirationTime
以及每个子节点的childExpirationTime
,completeUnitOfWork
是由低往上更新的过程,每个节点都会对应这个操作,因此每个节点都会更新到优先级最高的expirationTime
。对比appendChild
时的操作类似,都只会appendChild
第一层子节点的dom
。节点有报错的情况
Incomplete
的effect
,在completeUnitOfWork
时会判断存在Incomplete
则调用unwindWork
error boundary
的节点增加DidCapture
的effect
onUncaughtError
nextFlushedRoot.expirationTime = NoWork
取消当前root
的更新throwException
Incomplete
的effect
firstEffect、lastEffect
链suspened
异步组件抛出的promise
classComponent
,如果没有,那么会在hostRoot
上进行内置处理,root
节点肯定会标记ShouldCapture
classComponent
都会标记effect
为ShouldCapture
firstCapturedUpdate、lastCapturedUpdate链
加入到updateQueue
中getDerivedStateFromError
是在update.payload
函数里执行的componentDidCatch
是在update.callback
里执行的unwindWork
completeWork
对不同类型组件进行处理effect
为shouldCapture
的组件设置DidCapture
副作用ClassComponent、HostRoot、SuspenseComponent
操作,其他类型组件只是pop context
classComponent、SuspenseComponent
如果有shouldCapture
的effectTag
则会返回workInProgress
,否则返回null
HostRoot
总会返回workInProgress
,其他类型都返回null
completeWork
的区别在于会判断shouldCapture
,如果有shouldCapture
,那么去掉shouldCapture
并增加DidCapture
的effectTag