You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
const ReactDOM: Object = {
...
render(
element: React$Element<any>,
container: DOMContainer,
callback: ?Function,
) {
invariant(
isValidContainer(container),
'Target container is not a DOM element.',
);
if (__DEV__) {
warningWithoutStack(
!container._reactHasBeenPassedToCreateRootDEV,
'You are calling ReactDOM.render() on a container that was previously ' +
'passed to ReactDOM.%s(). This is not supported. ' +
'Did you mean to call root.render(element)?',
enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot',
);
}
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback,
);
},
...
}
function legacyCreateRootFromDOMContainer(
container: DOMContainer,
forceHydrate: boolean,
): Root {
const shouldHydrate =
forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
// First clear any existing content.
if (!shouldHydrate) {
let warned = false;
let rootSibling;
while ((rootSibling = container.lastChild)) {
if (__DEV__) {
if (
!warned &&
rootSibling.nodeType === ELEMENT_NODE &&
(rootSibling: any).hasAttribute(ROOT_ATTRIBUTE_NAME)
) {
warned = true;
warningWithoutStack(
false,
'render(): Target node has markup rendered by React, but there ' +
'are unrelated nodes as well. This is most commonly caused by ' +
'white-space inserted around server-rendered markup.',
);
}
}
container.removeChild(rootSibling);
}
}
if (__DEV__) {
if (shouldHydrate && !forceHydrate && !warnedAboutHydrateAPI) {
warnedAboutHydrateAPI = true;
lowPriorityWarning(
false,
'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
'will stop working in React v17. Replace the ReactDOM.render() call ' +
'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
);
}
}
// Legacy roots are not async by default.
const isConcurrent = false;
return new ReactRoot(container, isConcurrent, shouldHydrate);
}
export function createFiberRoot(
containerInfo: any,
isConcurrent: boolean,
hydrate: boolean,
): FiberRoot {
// Cyclic construction. This cheats the type system right now because
// stateNode is any.
const uninitializedFiber = createHostRootFiber(isConcurrent);
let root;
if (enableSchedulerTracing) {
root = ({
current: uninitializedFiber,
containerInfo: containerInfo,
pendingChildren: null,
earliestPendingTime: NoWork,
latestPendingTime: NoWork,
earliestSuspendedTime: NoWork,
latestSuspendedTime: NoWork,
latestPingedTime: NoWork,
pingCache: null,
didError: false,
pendingCommitExpirationTime: NoWork,
finishedWork: null,
timeoutHandle: noTimeout,
context: null,
pendingContext: null,
hydrate,
nextExpirationTimeToWorkOn: NoWork,
expirationTime: NoWork,
firstBatch: null,
nextScheduledRoot: null,
interactionThreadID: unstable_getThreadID(),
memoizedInteractions: new Set(),
pendingInteractionMap: new Map(),
}: FiberRoot);
} else {
...
}
uninitializedFiber.stateNode = root;
// The reason for the way the Flow types are structured in this file,
// Is to avoid needing :any casts everywhere interaction tracing fields are used.
// Unfortunately that requires an :any cast for non-interaction tracing capable builds.
// $FlowFixMe Remove this :any cast and replace it with something better.
return ((root: any): FiberRoot);
}
export function createHostRootFiber(isConcurrent: boolean): Fiber {
let mode = isConcurrent ? ConcurrentMode | StrictMode : NoContext;
if (enableProfilerTimer && isDevToolsPresent) {
// Always collect profile timings when DevTools are present.
// This enables DevTools to start capturing timing at any point–
// Without some nodes in the tree having empty base times.
mode |= ProfileMode;
}
return createFiber(HostRoot, null, null, mode);
}
function scheduleRootUpdate(current$$1, element, expirationTime, callback) {
{
if (phase === 'render' && current !== null && !didWarnAboutNestedUpdates) {
didWarnAboutNestedUpdates = true;
warningWithoutStack$1(false, 'Render methods should be a pure function of props and state; ' + 'triggering nested component updates from render is not allowed. ' + 'If necessary, trigger nested updates in componentDidUpdate.\n\n' + 'Check the render method of %s.', getComponentName(current.type) || 'Unknown');
}
}
var update = createUpdate(expirationTime);
// Caution: React DevTools currently depends on this property
// being called "element".
update.payload = { element: element };
callback = callback === undefined ? null : callback;
if (callback !== null) {
!(typeof callback === 'function') ? warningWithoutStack$1(false, 'render(...): Expected the last optional `callback` argument to be a ' + 'function. Instead received: %s.', callback) : void 0;
update.callback = callback;
}
flushPassiveEffects();
enqueueUpdate(current$$1, update);
scheduleWork(current$$1, expirationTime);
return expirationTime;
}
function scheduleWork(fiber, expirationTime) {
var root = scheduleWorkToRoot(fiber, expirationTime);
if (root === null) {
{
switch (fiber.tag) {
case ClassComponent:
warnAboutUpdateOnUnmounted(fiber, true);
break;
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent:
warnAboutUpdateOnUnmounted(fiber, false);
break;
}
}
return;
}
if (!isWorking && nextRenderExpirationTime !== NoWork && expirationTime > nextRenderExpirationTime) {
// This is an interruption. (Used for performance tracking.)
interruptedBy = fiber;
resetStack();
}
markPendingPriorityLevel(root, expirationTime);
if (
// If we're in the render phase, we don't need to schedule this root
// for an update, because we'll do it before we exit...
!isWorking || isCommitting$1 ||
// ...unless this is a different root than the one we're rendering.
nextRoot !== root) {
var rootExpirationTime = root.expirationTime;
requestWork(root, rootExpirationTime);
}
if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
// Reset this back to zero so subsequent updates don't throw.
nestedUpdateCount = 0;
invariant(false, 'Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.');
}
}
在 scheduleWork 函数中只需要关注 requestWork 函数就好。
function requestWork(root, expirationTime) {
addRootToSchedule(root, expirationTime);
if (isRendering) {
// Prevent reentrancy. Remaining work will be scheduled at the end of
// the currently rendering batch.
return;
}
if (isBatchingUpdates) {
// Flush work at the end of the batch.
if (isUnbatchingUpdates) {
// ...unless we're inside unbatchedUpdates, in which case we should
// flush it now.
nextFlushedRoot = root;
nextFlushedExpirationTime = Sync;
performWorkOnRoot(root, Sync, false);
}
return;
}
// TODO: Get rid of Sync and use current time?
if (expirationTime === Sync) {
performSyncWork();
} else {
scheduleCallbackWithExpirationTime(root, expirationTime);
}
}
addRootToSchedule 函数将 root 加入到 Schedule。
function addRootToSchedule(root, expirationTime) {
// Add the root to the schedule.
// Check if this root is already part of the schedule.
if (root.nextScheduledRoot === null) {
// This root is not already scheduled. Add it.
root.expirationTime = expirationTime;
if (lastScheduledRoot === null) {
firstScheduledRoot = lastScheduledRoot = root;
root.nextScheduledRoot = root;
} else {
lastScheduledRoot.nextScheduledRoot = root;
lastScheduledRoot = root;
lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
}
} else {
// This root is already scheduled, but its priority may have increased.
var remainingExpirationTime = root.expirationTime;
if (expirationTime > remainingExpirationTime) {
// Update the priority.
root.expirationTime = expirationTime;
}
}
}
function performWork(minExpirationTime, isYieldy) {
// Keep working on roots until there's no more work, or until there's a higher
// priority event.
findHighestPriorityRoot();
if (isYieldy) {
recomputeCurrentRendererTime();
currentSchedulerTime = currentRendererTime;
if (enableUserTimingAPI) {
var didExpire = nextFlushedExpirationTime > currentRendererTime;
var timeout = expirationTimeToMs(nextFlushedExpirationTime);
stopRequestCallbackTimer(didExpire, timeout);
}
while (nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork && minExpirationTime <= nextFlushedExpirationTime && !(didYield && currentRendererTime > nextFlushedExpirationTime)) {
performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, currentRendererTime > nextFlushedExpirationTime);
findHighestPriorityRoot();
recomputeCurrentRendererTime();
currentSchedulerTime = currentRendererTime;
}
} else {
while (nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork && minExpirationTime <= nextFlushedExpirationTime) {
performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, false);
findHighestPriorityRoot();
}
}
// We're done flushing work. Either we ran out of time in this callback,
// or there's no more work left with sufficient priority.
// If we're inside a callback, set this to false since we just completed it.
if (isYieldy) {
callbackExpirationTime = NoWork;
callbackID = null;
}
// If there's work left over, schedule a new callback.
if (nextFlushedExpirationTime !== NoWork) {
scheduleCallbackWithExpirationTime(nextFlushedRoot, nextFlushedExpirationTime);
}
// Clean-up.
finishRendering();
}
findHighestPriorityRoot 函数会找出优先级高的 root(FiberRoot)。因为 isYieldy 是 false,所以会走 while 循环,然后调用 performWorkOnRoot 函数。
function performWorkOnRoot(root, expirationTime, isYieldy) {
!!isRendering ? invariant(false, 'performWorkOnRoot was called recursively. This error is likely caused by a bug in React. Please file an issue.') : void 0;
isRendering = true;
// Check if this is async work or sync/expired work.
if (!isYieldy) {
// Flush work without yielding.
// TODO: Non-yieldy work does not necessarily imply expired work. A renderer
// may want to perform some work without yielding, but also without
// requiring the root to complete (by triggering placeholders).
var finishedWork = root.finishedWork;
if (finishedWork !== null) {
// This root is already complete. We can commit it.
completeRoot(root, finishedWork, expirationTime);
} else {
root.finishedWork = null;
// If this root previously suspended, clear its existing timeout, since
// we're about to try rendering again.
var timeoutHandle = root.timeoutHandle;
if (timeoutHandle !== noTimeout) {
root.timeoutHandle = noTimeout;
// $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
cancelTimeout(timeoutHandle);
}
renderRoot(root, isYieldy);
finishedWork = root.finishedWork;
if (finishedWork !== null) {
// We've completed the root. Commit it.
completeRoot(root, finishedWork, expirationTime);
}
}
} else {
...
}
isRendering = false;
}
function workLoop(isYieldy) {
if (!isYieldy) {
// Flush work without yielding
while (nextUnitOfWork !== null) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
} else {
// Flush asynchronous work until there's a higher priority event
while (nextUnitOfWork !== null && !shouldYieldToRenderer()) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
}
}
因为 isYieldy 是 false,所以进入 if 语句,调用 performUnitOfWork 函数。这里是一个 while 循环,遍历 React 元素(DOM 节点)是深度优先。
function performUnitOfWork(workInProgress) {
// 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$$1 = workInProgress.alternate;
// See if beginning this work spawns more work.
startWorkTimer(workInProgress);
{
setCurrentFiber(workInProgress);
}
if (true && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
stashedWorkInProgressProperties = assignFiberPropertiesInDEV(stashedWorkInProgressProperties, workInProgress);
}
var next = void 0;
if (enableProfilerTimer) {
if (workInProgress.mode & ProfileMode) {
startProfilerTimer(workInProgress);
}
next = beginWork(current$$1, workInProgress, nextRenderExpirationTime);
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$$1, workInProgress, nextRenderExpirationTime);
workInProgress.memoizedProps = workInProgress.pendingProps;
}
{
resetCurrentFiber();
if (isReplayingFailedUnitOfWork) {
// Currently replaying a failed unit of work. This should be unreachable,
// because the render phase is meant to be idempotent, and it should
// have thrown again. Since it didn't, rethrow the original error, so
// React's internal stack is not misaligned.
rethrowOriginalError();
}
}
if (true && ReactFiberInstrumentation_1.debugTool) {
ReactFiberInstrumentation_1.debugTool.onBeginWork(workInProgress);
}
if (next === null) {
// If this doesn't spawn new work, complete the current work.
next = completeUnitOfWork(workInProgress);
}
ReactCurrentOwner$2.current = null;
return next;
}
在 performUnitOfWork 函数中调用 beginWork。
function beginWork(current$$1, workInProgress, renderExpirationTime) {
var updateExpirationTime = workInProgress.expirationTime;
...
// Before entering the begin phase, clear the expiration time.
workInProgress.expirationTime = NoWork;
switch (workInProgress.tag) {
case IndeterminateComponent:{...}
case LazyComponent:{...}
case FunctionComponent:{...}
case ClassComponent:{...}
case HostRoot:{...}
case HostText:{...}
case SuspenseComponent:{...}
case HostPortal:{...}
case ForwardRef:{...}
case Fragment:{...}
case Mode:{...}
case Profiler:{...}
case ContextProvider:{...}
case ContextConsumer:{...}
case MemoComponent:{...}
case SimpleMemoComponent:{...}
case IncompleteClassComponent:{...}
case DehydratedSuspenseComponent:{...}
invariant(...);
}
function completeWork(current, workInProgress, renderExpirationTime) {
var newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case IndeterminateComponent:
break;
case LazyComponent:
break;
case SimpleMemoComponent:
case FunctionComponent:
break;
case ClassComponent:
{
var Component = workInProgress.type;
if (isContextProvider(Component)) {
popContext(workInProgress);
}
break;
}
case HostRoot: {...}
case HostComponent: {...}
case HostText: {...}
case ForwardRef:
break;
case SuspenseComponent: {...}
case Fragment:
break;
case Mode:
break;
case Profiler:
break;
case HostPortal:
popHostContainer(workInProgress);
updateHostContainer(workInProgress);
break;
case ContextProvider:
// Pop provider fiber
popProvider(workInProgress);
break;
case ContextConsumer:
break;
case MemoComponent:
break;
case IncompleteClassComponent: {...}
case DehydratedSuspenseComponent: {...}
default: {...}
return null;
}
completeWork 根据不同类型节点做不同处理,对于大部分 tag 不处理或者 pop context。HostComponent, HostText, SuspenseComponent 会有比较复杂的处理。这里以 HostComponent 为例说明。
case HostComponent:
{
popHostContext(workInProgress);
var rootContainerInstance = getRootHostContainer();
var type = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent$1(current, workInProgress, type, newProps, rootContainerInstance);
if (current.ref !== workInProgress.ref) {
markRef$1(workInProgress);
}
} else {
if (!newProps) {
!(workInProgress.stateNode !== null) ? invariant(false, 'We must have new props for new mounts. This error is likely caused by a bug in React. Please file an issue.') : void 0;
// This can happen when we abort work.
break;
}
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 we want to add then top->down or
// bottom->up. Top->down is faster in IE11.
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 needs to be applied at the
// commit-phase we mark this as such.
markUpdate(workInProgress);
}
} else {
var instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress);
appendAllChildren(instance, workInProgress, false, false);
// 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, currentHostContext)) {
markUpdate(workInProgress);
}
workInProgress.stateNode = instance;
}
if (workInProgress.ref !== null) {
// If there is a ref on a host node we need to schedule a callback
markRef$1(workInProgress);
}
}
break;
}
当 tag 为 HostComponent 时表示普通 dom 节点。在处理 HostCoponent 时,分为第一次渲染和以后重新渲染逻辑。如果没有 current 就是第一次渲染,主要调用下面三个函数:
createInstance 创建 dom
appendAllChildren 将新创建的 dom 节点添加到 dom 树上
finalizeInitialChildren 给 dom 设置属性
markUpdate 设置 effectTag 值
createInstance 函数:
function createInstance(type, props, rootContainerInstance, hostContext, internalInstanceHandle) {
var parentNamespace = void 0;
{
// TODO: take namespace into account when validating.
var hostContextDev = hostContext;
validateDOMNesting(type, null, hostContextDev.ancestorInfo);
if (typeof props.children === 'string' || typeof props.children === 'number') {
var string = '' + props.children;
var ownAncestorInfo = updatedAncestorInfo(hostContextDev.ancestorInfo, type);
validateDOMNesting(null, string, ownAncestorInfo);
}
parentNamespace = hostContextDev.namespace;
}
var domElement = createElement(type, props, rootContainerInstance, parentNamespace);
precacheFiberNode(internalInstanceHandle, domElement);
updateFiberProps(domElement, props);
return domElement;
}
createElement 创建 dom 节点,precacheFiberNode 将 dom 节点指向对应的 fiber 节点,updateFiberProps 在 dom 节点的上添加 props 属性。
appendAllChildren 函数:
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) {
// 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) {
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;
}
};
appendAllChildren 函数作用是将下一层的 dom 节点 append 到父节点上。流程如下:
function markUpdate(workInProgress) {
// Tag the fiber with an update effect. This turns a Placement into
// a PlacementAndUpdate.
workInProgress.effectTag |= Update;
}
updateHostComponent$1 = function (current, workInProgress, type, newProps, rootContainerInstance) {
// If we have an alternate, that means this is an update and we need to
// schedule a side-effect to do the updates.
var oldProps = current.memoizedProps;
if (oldProps === newProps) {
// In mutation mode, this is sufficient for a bailout because
// we won't touch this node even if children changed.
return;
}
// If we get updated because one of our children updated, we don't
// have newProps so we'll have to reuse them.
// TODO: Split the update API as separate for the props vs. children.
// Even better would be if children weren't special cased at all tho.
var instance = workInProgress.stateNode;
var 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`.
var updatePayload = prepareUpdate(instance, type, oldProps, newProps, rootContainerInstance, currentHostContext);
// TODO: Type this specific to this type of component.
workInProgress.updateQueue = updatePayload;
// 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.
if (updatePayload) {
markUpdate(workInProgress);
}
};
function diffProperties(domElement, tag, lastRawProps, nextRawProps, rootContainerElement) {
{
validatePropertiesInDevelopment(tag, nextRawProps);
}
var updatePayload = null;
var lastProps = void 0;
var nextProps = void 0;
// 根据不同节点类型提取 lastProps nextProps
switch (tag) {
case 'input':
lastProps = getHostProps(domElement, lastRawProps);
nextProps = getHostProps(domElement, nextRawProps);
updatePayload = [];
break;
case 'option':
lastProps = getHostProps$1(domElement, lastRawProps);
nextProps = getHostProps$1(domElement, nextRawProps);
updatePayload = [];
break;
case 'select':
lastProps = getHostProps$2(domElement, lastRawProps);
nextProps = getHostProps$2(domElement, nextRawProps);
updatePayload = [];
break;
case 'textarea':
lastProps = getHostProps$3(domElement, lastRawProps);
nextProps = getHostProps$3(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);
}
break;
}
assertValidProps(tag, nextProps);
var propKey = void 0;
var styleName = void 0;
var styleUpdates = null;
// 遍历lastProps,将要删除的属性设置为 null
for (propKey in lastProps) {
if (nextProps.hasOwnProperty(propKey) || !lastProps.hasOwnProperty(propKey) || lastProps[propKey] == null) {
continue;
}
if (propKey === STYLE$1) {
var lastStyle = lastProps[propKey];
for (styleName in lastStyle) {
if (lastStyle.hasOwnProperty(styleName)) {
if (!styleUpdates) {
styleUpdates = {};
}
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$1) {
// 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.
(updatePayload = updatePayload || []).push(propKey, null);
}
}
// 遍历 nextProps,将新的 props push 到 updatePayload
for (propKey in nextProps) {
var nextProp = nextProps[propKey];
var lastProp = lastProps != null ? lastProps[propKey] : undefined;
if (!nextProps.hasOwnProperty(propKey) || nextProp === lastProp || nextProp == null && lastProp == null) {
continue;
}
if (propKey === STYLE$1) {
{
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 (lastProp.hasOwnProperty(styleName) && (!nextProp || !nextProp.hasOwnProperty(styleName))) {
if (!styleUpdates) {
styleUpdates = {};
}
styleUpdates[styleName] = '';
}
}
// Update styles that changed since `lastProp`.
for (styleName in nextProp) {
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 = [];
}
updatePayload.push(propKey, styleUpdates);
}
styleUpdates = nextProp;
}
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
var nextHtml = nextProp ? nextProp[HTML] : undefined;
var 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) {
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$1) {
// Noop
} else if (registrationNameModules.hasOwnProperty(propKey)) {
if (nextProp != null) {
// We eagerly listen to this even though we haven't committed yet.
if (true && 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) {
{
validateShorthandPropertyCollisionInDev(styleUpdates, nextProps[STYLE$1]);
}
(updatePayload = updatePayload || []).push(STYLE$1, styleUpdates);
}
return updatePayload;
}
React 源码解析: render 流程
这里开始解析 ReactDOM.render 函数
render 函数定义在 ReactDOM.js 文件中
render 函数本身很简单,就是调用了 legacyRenderSubtreeIntoContainer 函数。legacyRenderSubtreeIntoContainer 函数还是定义在 ReactDOM.js 文件中
进入函数首先判断是否有 root,开始进入肯定是没有 root 的,所以进入 if (!root) 语句中。通过函数 legacyCreateRootFromDOMContainer 创建 root 挂载在 container._reactRootContainer 上。legacyCreateRootFromDOMContainer 函数会返回 ReactRoot 实例。
其中 forceHydrate 参数一直是 fasle, shouldHydrate 为 false。
legacyCreateRootFromDOMContainer 函数除了创建 root 外,还有一个作用是删除 container 的子元素。所以在 container 中不要有子元素,即使有最终也会被删除的。最后通过 ReactRoot 函数创建 root。
可以看到 ReactRoot 的原型上有:render、unmount、legacy_renderSubtreeIntoContainer、createBatch 函数。
ReactRoot 函数是通过 createContainer 创建 root 的,然后将 root 挂载到 ReactRoot 实例的 _internalRoot 属性上。
createContainer 函数通过 createFiberRoot 创建 root。
在 createFiberRoot 函数中最终返回 FiberRoot 对象,FiberRoot 对象有很多属性,这里我们只关注 current 属性,current 属性值是通过 createHostRootFiber 函数创建的。
createHostRootFiber 函数通过 createFiber 返回 RootFiber。
createFiberRoot 函数创建了两个 root:FiberRoot 和 RootFiber,它们相互引用。FiberRoot.current = RootFiber,RootFiber.stateNode = FiberRoot
梳理一下几个 root 的关系:
回到 legacyRenderSubtreeIntoContainer 函数中。
这个 if 语句是处理 callback,如果存在 callback ,重新包装 callback。重新包装的目的是通过 call 函数将 callback 函数的 this 变为 getPublicRootInstance(root._internalRoot)。
一般 parentComponent 为空,所以直接看 root.render 函数。root.render 就是 ReactRoot 构造函数原型的 render 函数。
render 函数中 this._internalRoot 是 FiberRoot。ReactWork 简单理解为缓存 callback,在必要的时候拿出执行。
然后会执行 updateContainer 函数。
这里不讨论 expirationTime 问题。
updateContainerAtExpirationTime 函数又会调用 scheduleRootUpdate 函数。
创建 update 对象,添加 payload 和 callback 属性。
接下来调用三个函数:flushPassiveEffects、enqueueUpdate、scheduleWork。
首次渲染时,flushPassiveEffects 什么都没做。
调用 enqueueUpdate 函数,两个参数分别是 RootFiber 和 新建的 update 对象。enqueueUpdate 函数为 RootFiber 添加了 updateQueue 对象,里面保存了相关任务。
最后调用 scheduleWork 函数。
在 scheduleWork 函数中只需要关注 requestWork 函数就好。
addRootToSchedule 函数将 root 加入到 Schedule。
如果 root 已经调用过,root.nextScheduledRoot 不为空,可能更新 root.expirationTime。如果 root.nextScheduledRoot 为空,表示 root 还没有调用过,那么需要重新维护一个 scheduledRoot 链表。
如果 lastScheduledRoot 为空,表示已经没有要处理的 root,重新所以将 firstScheduleRoot、lastScheduledRoot、root.nextScheduledRoot 置为 root。否则还有需要处理的 root,那么将 lastScheduledRoot.nextScheduledRoot、lastScheduledRoot 置为 root,将 lastScheduledRoot.nextScheduledRoot 置为 firstScheduleRoot。
回到 requestWork 函数。如果 isRendering 为 true,直接返回。isRender 在 performWorkOnRoot 函数中置为 true。
如果 isBatchingUpdates 为 true,也是直接返回。isBatchingUpdates 和批量更新相关,在分析 setState 时会遇见。
因为暂时我们不关注 expirationTime,所以先看同步方式 performSyncWork。performSyncWork 函数调用 performWork。
findHighestPriorityRoot 函数会找出优先级高的 root(FiberRoot)。因为 isYieldy 是 false,所以会走 while 循环,然后调用 performWorkOnRoot 函数。
首先将 isRendering 置为 true,然后调用 renderRoot。renderRoot 函数代码非常多,这里就不贴出来了。可以在 react-dom.js 文件查看。
renderRoot 函数将 isWorking 变量置为 true,将 root 赋值给 nextRoot。
接下来有一个重要的函数 createWorkInProgress,这个函数的作用就是创建 fiber 节点的副本。这里 createWorkInProgress 函数会拷贝一份 fiber 节点赋值到变量 nextUnitOfWork 上。在 createWorkInProgress 函数中有这样一段代码:
workInProgress 和 current 通过 alternate 属性相互引用。
然后是 workLoop 函数,这个函数非常重要,它的作用是:将 React 元素创建成相应的 Fiber,并形成 Fiber 树。
因为 isYieldy 是 false,所以进入 if 语句,调用 performUnitOfWork 函数。这里是一个 while 循环,遍历 React 元素(DOM 节点)是深度优先。
在 performUnitOfWork 函数中调用 beginWork。
在 beginWork 函数中,首先是和 expirationTime (优先级)相关,这里先忽略。然后会根据 Fiber 节点类型(Fiber 节点类型定义在 ReactWorkTags.js 文件)分别处理。无论是什么类型的 Fiber 节点,处理的目的是:Fiber 节点是否需要处理(更新,删除,添加等)。
beginWork 函数每次都会返回当前节点的第一个 child(即使有多个 child),这样就会沿着当前 Fiber 节点树深度优先规则一直更新直到 null。
回到 performUnitOfWork 函数中,如果 beginWork 函数返回 null,就进入 completeUnitOfWork 函数。completeUnitOfWork 函数作用:完成当前节点(completeWork)、赋值 effect 链、返回兄弟节点。
进入 completeWork 函数:
completeWork 根据不同类型节点做不同处理,对于大部分 tag 不处理或者 pop context。HostComponent, HostText, SuspenseComponent 会有比较复杂的处理。这里以 HostComponent 为例说明。
当 tag 为 HostComponent 时表示普通 dom 节点。在处理 HostCoponent 时,分为第一次渲染和以后重新渲染逻辑。如果没有 current 就是第一次渲染,主要调用下面三个函数:
createInstance 函数:
createElement 创建 dom 节点,precacheFiberNode 将 dom 节点指向对应的 fiber 节点,updateFiberProps 在 dom 节点的上添加 props 属性。
appendAllChildren 函数:
appendAllChildren 函数作用是将下一层的 dom 节点 append 到父节点上。流程如下:
finalizeInitialChildren 函数:
通过 setInitialProperties 函数对 dom 节点设置一些初始值。
markUpdate 函数
markUpdate 设置 fiber 节点的 effectTag。
如果不是第一次渲染,那么会进入 if 语句:
进入 updateHostComponent$1 函数:
如果 oldProps 等于 newProps,那么直接返回。
然后调用 prepareUpdate 函数,得到新老 props 比较后的结果,并将结果赋值到 workInProgress.updateQueue。
最后调用 markUpdate 函数。
prepareUpdate 函数会调用 diffProperties 函数。diffProperties 处理步骤:
回到 completeUnitOfWork 函数中,接下来是完成 effect 链,最后判断当前节点是否有 siblingFiber,有就返回 siblingFiber,没有就继续循环。
所有 render 流程已经完成,当然这里只是将大概流程走了一遍,如果对于其中不了解或不理解的,着重分析就好,下一篇将分析 commit 流程。
The text was updated successfully, but these errors were encountered: