跳到主要内容

React Fiber 水合上下文

一、作用

二、警示或报错报错

1. 水合警示

function warnIfHydrating() {
if (__DEV__) {
if (isHydrating) {
console.error(
'We should not be hydrating here. This is a bug in React. Please file a bug.',
);
}
}
}

2. 标记在开发时抛出异常的水合操作

export function markDidThrowWhileHydratingDEV() {
if (__DEV__) {
didSuspendOrErrorDEV = true;
}
}

三、 输入水合状态

备注
  • supportsHydration 由渲染平台提供
  • getFirstHydratableChildWithinContainer() 由渲染平台实现
function enterHydrationState(fiber: Fiber): boolean {
if (!supportsHydration) {
return false;
}
const parentInstance: Container = fiber.stateNode.containerInfo;
nextHydratableInstance =
getFirstHydratableChildWithinContainer(parentInstance);
hydrationParentFiber = fiber;
isHydrating = true;
hydrationErrors = null;
didSuspendOrErrorDEV = false;
hydrationDiffRootDEV = null;
rootOrSingletonContext = true;
return true;
}

四、从脱水活动实例重新进入水合状态

备注
function reenterHydrationStateFromDehydratedActivityInstance(
fiber: Fiber,
activityInstance: ActivityInstance,
treeContext: TreeContext | null,
): boolean {
if (!supportsHydration) {
return false;
}
nextHydratableInstance =
getFirstHydratableChildWithinActivityInstance(activityInstance);
hydrationParentFiber = fiber;
isHydrating = true;
hydrationErrors = null;
didSuspendOrErrorDEV = false;
hydrationDiffRootDEV = null;
rootOrSingletonContext = false;
if (treeContext !== null) {
restoreSuspendedTreeContext(fiber, treeContext);
}
return true;
}

五、从脱水的挂起实例重新进入水合状态

备注
function reenterHydrationStateFromDehydratedSuspenseInstance(
fiber: Fiber,
suspenseInstance: SuspenseInstance,
treeContext: TreeContext | null,
): boolean {
if (!supportsHydration) {
return false;
}
nextHydratableInstance =
getFirstHydratableChildWithinSuspenseInstance(suspenseInstance);
hydrationParentFiber = fiber;
isHydrating = true;
hydrationErrors = null;
didSuspendOrErrorDEV = false;
hydrationDiffRootDEV = null;
rootOrSingletonContext = false;
if (treeContext !== null) {
restoreSuspendedTreeContext(fiber, treeContext);
}
return true;
}

六、水合不匹配异常

export const HydrationMismatchException: mixed = new Error(
'Hydration Mismatch Exception: This is not a real error, and should not leak into ' +
"userspace. If you're seeing this, it's likely a bug in React.",
);

七、队列水合错误

export function queueHydrationError(error: CapturedValue<mixed>): void {
if (hydrationErrors === null) {
hydrationErrors = [error];
} else {
hydrationErrors.push(error);
}
}

八、请求可水合单例

备注
function claimHydratableSingleton(fiber: Fiber): void {
if (supportsSingletons) {
if (!isHydrating) {
return;
}
const currentRootContainer = getRootHostContainer();
const currentHostContext = getHostContext();
const instance = (fiber.stateNode = resolveSingletonInstance(
fiber.type,
fiber.pendingProps,
currentRootContainer,
currentHostContext,
false,
));

if (__DEV__) {
if (!didSuspendOrErrorDEV) {
const differences = diffHydratedPropsForDevWarnings(
instance,
fiber.type,
fiber.pendingProps,
currentHostContext,
);
if (differences !== null) {
// + 该方法本文档实现
const diffNode = buildHydrationDiffNode(fiber, 0);
diffNode.serverProps = differences;
}
}
}

hydrationParentFiber = fiber;
rootOrSingletonContext = true;
nextHydratableInstance = getFirstHydratableChildWithinSingleton(
fiber.type,
instance,
nextHydratableInstance,
);
}
}

九、尝试认领下一个可水合作用实例

备注
  • validateHydratableInstance() 由渲染平台实现
function tryToClaimNextHydratableInstance(fiber: Fiber): void {
if (!isHydrating) {
return;
}

// Validate that this is ok to render here before any mismatches.
// 在出现任何不匹配之前,先验证这里是否可以渲染。
const currentHostContext = getHostContext(); // 本文档实现
const shouldKeepWarning = validateHydratableInstance(
fiber.type,
fiber.pendingProps,
currentHostContext,
);

const nextInstance = nextHydratableInstance;
if (
!nextInstance ||
// 本文档实现
!tryHydrateInstance(fiber, nextInstance, currentHostContext)
) {
if (shouldKeepWarning) {
// 本文档实现
warnNonHydratedInstance(fiber, nextInstance);
}
// 本文档实现
throwOnHydrationMismatch(fiber);
}
}

十、尝试声明下一个可水合的文本实例

备注
function tryToClaimNextHydratableTextInstance(fiber: Fiber): void {
if (!isHydrating) {
return;
}
const text = fiber.pendingProps;

let shouldKeepWarning = true;
// Validate that this is ok to render here before any mismatches.
// 在出现任何不匹配之前,先验证这里是否可以渲染。
const currentHostContext = getHostContext();
shouldKeepWarning = validateHydratableTextInstance(text, currentHostContext);

const nextInstance = nextHydratableInstance;
if (
!nextInstance ||
// 本文档实现
!tryHydrateText(fiber, nextInstance)
) {
if (shouldKeepWarning) {
// 本文档实现
warnNonHydratedInstance(fiber, nextInstance);
}
// 本文档实现
throwOnHydrationMismatch(fiber);
}
}

十一、请求下一个可水合的活动实例

function claimNextHydratableActivityInstance(fiber: Fiber): ActivityInstance {
const nextInstance = nextHydratableInstance;
const activityInstance = nextInstance
? // 本文档实现
tryHydrateActivity(fiber, nextInstance)
: null;
if (activityInstance === null) {
// 本文档实现
warnNonHydratedInstance(fiber, nextInstance);
// 本文档实现
throw throwOnHydrationMismatch(fiber);
}
return activityInstance;
}

十二、声明下一个可水合的挂起实例

function claimNextHydratableSuspenseInstance(fiber: Fiber): SuspenseInstance {
const nextInstance = nextHydratableInstance;
const suspenseInstance = nextInstance
? // 本文档实现
tryHydrateSuspense(fiber, nextInstance)
: null;
if (suspenseInstance === null) {
// 本文档实现
warnNonHydratedInstance(fiber, nextInstance);
// 本文档实现
throw throwOnHydrationMismatch(fiber);
}
return suspenseInstance;
}

十三、尝试获取下一个可水化的表单标记实例

备注
  • canHydrateFormStateMarker() 由渲染平台实现
  • getNextHydratableSibling() 由渲染平台实现
  • isFormStateMarkerMatching() 由渲染平台实现
export function tryToClaimNextHydratableFormMarkerInstance(
fiber: Fiber,
): boolean {
if (!isHydrating) {
return false;
}
if (nextHydratableInstance) {
const markerInstance = canHydrateFormStateMarker(
nextHydratableInstance,
rootOrSingletonContext,
);
if (markerInstance) {
// Found the marker instance.
// 找到标记实例。
nextHydratableInstance = getNextHydratableSibling(markerInstance);
// Return true if this marker instance should use the state passed
// to hydrateRoot.
//
// 如果此标记实例应使用传递的状态来 hydrateRoot,则返回 true。
//
// TODO: As an optimization, Fizz should only emit these markers if form
// state is passed at the root.
//
// TODO:作为一种优化,只有在根传递了表单状态时,Fizz 才应该发出这些标记。
return isFormStateMarkerMatching(markerInstance);
}
}
// Should have found a marker instance. Throw an error to trigger client
// rendering. We don't bother to check if we're in a concurrent root because
// useActionState is a new API, so backwards compat is not an issue.
//
// 应该已经找到一个标记实例。抛出错误以触发客户端渲染。
// 我们不需要检查是否在并发根中,因为 useActionState 是一个新 API,所以向后兼容不是问题。
throwOnHydrationMismatch(fiber); // 本文档实现
return false;
}

十四、准备水h合宿主实例

备注
  • hydrateInstance() 由渲染平台实现
function prepareToHydrateHostInstance(
fiber: Fiber,
hostContext: HostContext,
): void {
if (!supportsHydration) {
throw new Error(
'Expected prepareToHydrateHostInstance() to never be called. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
}

const instance: Instance = fiber.stateNode;
const didHydrate = hydrateInstance(
instance,
fiber.type,
fiber.memoizedProps,
hostContext,
fiber,
);
if (!didHydrate) {
// + 本页面实现
throwOnHydrationMismatch(fiber, true);
}
}

十五、准备水合宿主文本实例

备注
  • diffHydratedTextForDevWarnings() 由渲染平台实现
  • hydrateTextInstance() 由渲染平台实现
function prepareToHydrateHostTextInstance(fiber: Fiber): void {
if (!supportsHydration) {
throw new Error(
'Expected prepareToHydrateHostTextInstance() to never be called. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
}

const textInstance: TextInstance = fiber.stateNode;
const textContent: string = fiber.memoizedProps;
const shouldWarnIfMismatchDev = !didSuspendOrErrorDEV;
let parentProps = null;
// We assume that prepareToHydrateHostTextInstance is called in a context where the
// hydration parent is the parent host component of this host text.
//
// 我们假设 prepareToHydrateHostTextInstance 在一个上下文中被调用,
// 其中水化父节点是该宿主文本的父宿主组件。
const returnFiber = hydrationParentFiber;
if (returnFiber !== null) {
switch (returnFiber.tag) {
case HostRoot: {
if (__DEV__) {
if (shouldWarnIfMismatchDev) {
const difference = diffHydratedTextForDevWarnings(
textInstance,
textContent,
parentProps,
);
if (difference !== null) {
// 本文档实现
const diffNode = buildHydrationDiffNode(fiber, 0);
diffNode.serverProps = difference;
}
}
}
break;
}
case HostSingleton:
case HostComponent: {
parentProps = returnFiber.memoizedProps;
if (__DEV__) {
if (shouldWarnIfMismatchDev) {
const difference = diffHydratedTextForDevWarnings(
textInstance,
textContent,
parentProps,
);
if (difference !== null) {
const diffNode = buildHydrationDiffNode(fiber, 0);
diffNode.serverProps = difference;
}
}
}
break;
}
}
// TODO: What if it's a SuspenseInstance?
// 待办:如果它是 SuspenseInstance 会怎么样?
}

const didHydrate = hydrateTextInstance(
textInstance,
textContent,
fiber,
parentProps,
);
if (!didHydrate) {
// 本文档实现
throwOnHydrationMismatch(fiber, true);
}
}

十六、准备水合宿主活动实例

备注
  • hydrateActivityInstance() 由渲染平台实现
function prepareToHydrateHostActivityInstance(fiber: Fiber): void {
if (!supportsHydration) {
throw new Error(
'Expected prepareToHydrateHostActivityInstance() to never be called. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
}
const activityState: null | ActivityState = fiber.memoizedState;
const activityInstance: null | ActivityInstance =
activityState !== null ? activityState.dehydrated : null;

if (!activityInstance) {
throw new Error(
'Expected to have a hydrated activity instance. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
}

hydrateActivityInstance(activityInstance, fiber);
}

十七、准备水合宿主挂起实例

备注
  • hydrateSuspenseInstance() 由渲染平台实现
function prepareToHydrateHostSuspenseInstance(fiber: Fiber): void {
if (!supportsHydration) {
throw new Error(
'Expected prepareToHydrateHostSuspenseInstance() to never be called. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
}

const suspenseState: null | SuspenseState = fiber.memoizedState;
const suspenseInstance: null | SuspenseInstance =
suspenseState !== null ? suspenseState.dehydrated : null;

if (!suspenseInstance) {
throw new Error(
'Expected to have a hydrated suspense instance. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
}

hydrateSuspenseInstance(suspenseInstance, fiber);
}

十八、弹出水化状态

备注
  • shouldDeleteUnhydratedTailInstances() 由渲染平台实现
  • getNextHydratableSiblingAfterSingleton() 由渲染平台实现
  • getNextHydratableSibling() 由渲染平台实现
function popHydrationState(fiber: Fiber): boolean {
if (!supportsHydration) {
return false;
}
if (fiber !== hydrationParentFiber) {
// We're deeper than the current hydration context, inside an inserted
// tree.
// 我们比当前的 hydration 上下文更深,位于一个插入的树中。
return false;
}
if (!isHydrating) {
// If we're not currently hydrating but we're in a hydration context, then
// we were an insertion and now need to pop up reenter hydration of our
// siblings.
//
// 如果我们当前没有进行水合,但处于水合环境中,
// 那么我们之前是一个插入,现在需要弹出并重新进入我们兄弟节点的水合。
// + 本文档实现
popToNextHostParent(fiber);
isHydrating = true;
return false;
}

const tag = fiber.tag;

if (supportsSingletons) {
// With float we never clear the Root, or Singleton instances. We also do not clear Instances
// that have singleton text content
//
// 使用 float 时,我们从不清除 Root 或 Singleton 实例。我们也不会清除那些具有单例文本内容的实例
if (
tag !== HostRoot &&
tag !== HostSingleton &&
!(
tag === HostComponent &&
(!shouldDeleteUnhydratedTailInstances(fiber.type) ||
shouldSetTextContent(fiber.type, fiber.memoizedProps))
)
) {
const nextInstance = nextHydratableInstance;
if (nextInstance) {
// 本文档实现
warnIfUnhydratedTailNodes(fiber);
// 本文档实现
throwOnHydrationMismatch(fiber);
}
}
} else {
// If we have any remaining hydratable nodes, we need to delete them now.
// We only do this deeper than head and body since they tend to have random
// other nodes in them. We also ignore components with pure text content in
// side of them. We also don't delete anything inside the root container.
//
// 如果我们还有剩余的可水化节点,需要现在删除它们。
// 我们只在 head 和 body 之外的更深层次执行此操作,因为它们通常包含随机的其他节点。
// 我们也会忽略内部仅包含纯文本内容的组件。
// 我们也不会删除根容器内的任何内容。
if (
tag !== HostRoot &&
(tag !== HostComponent ||
(shouldDeleteUnhydratedTailInstances(fiber.type) &&
// 本文档实现
!shouldSetTextContent(fiber.type, fiber.memoizedProps)))
) {
const nextInstance = nextHydratableInstance;
if (nextInstance) {
// 本文档实现
warnIfUnhydratedTailNodes(fiber);
// 本文档实现
throwOnHydrationMismatch(fiber);
}
}
}
// 本文档实现
popToNextHostParent(fiber);
if (tag === SuspenseComponent) {
// 本文档实现
nextHydratableInstance = skipPastDehydratedSuspenseInstance(fiber);
} else if (tag === ActivityComponent) {
// 本文档实现
nextHydratableInstance = skipPastDehydratedActivityInstance(fiber);
} else if (supportsSingletons && tag === HostSingleton) {
nextHydratableInstance = getNextHydratableSiblingAfterSingleton(
fiber.type,
nextHydratableInstance,
);
} else {
nextHydratableInstance = hydrationParentFiber
? getNextHydratableSibling(fiber.stateNode)
: null;
}
return true;
}

十九、 重置水化状态

function resetHydrationState(): void {
if (!supportsHydration) {
return;
}

hydrationParentFiber = null;
nextHydratableInstance = null;
isHydrating = false;
didSuspendOrErrorDEV = false;
}

二十、将水合错误升级为可恢复

备注
export function upgradeHydrationErrorsToRecoverable(): Array<
CapturedValue<mixed>
> | null {
const queuedErrors = hydrationErrors;
if (queuedErrors !== null) {
// Successfully completed a forced client render. The errors that occurred
// during the hydration attempt are now recovered. We will log them in
// commit phase, once the entire tree has finished.
//

// 成功完成了强制客户端渲染。在尝试水合期间发生的错误现已恢复。
// 一旦整个树完成,我们将在提交阶段记录这些错误。
queueRecoverableErrors(queuedErrors);
hydrationErrors = null;
}
return queuedErrors;
}

廿一、获取是否正水合

function getIsHydrating(): boolean {
return isHydrating;
}

廿二、发出待处理的水合警告

备注
export function emitPendingHydrationWarnings() {
if (__DEV__) {
// If we haven't yet thrown any hydration errors by the time we reach the end we've successfully
// hydrated, however, we might still have DEV-only mismatches that we log now.
//
// 如果在到达末尾时我们还没有抛出任何 hydration 错误,那么我们已经成功完成了 hydration,
// 但是,我们可能仍然有仅在开发环境下出现的不匹配,我们现在会记录这些。
const diffRoot = hydrationDiffRootDEV;
if (diffRoot !== null) {
hydrationDiffRootDEV = null;
const diff = describeDiff(diffRoot);

// Just pick the DFS-first leaf as the owner.
// Should be good enough since most warnings only have a single error.
//
// 只需选择 DFS 优先的叶子作为所有者。
// 应该足够,因为大多数警告只有一个错误。
let diffOwner: HydrationDiffNode = diffRoot;
while (diffOwner.children.length > 0) {
diffOwner = diffOwner.children[0];
}

runWithFiberInDEV(diffOwner.fiber, () => {
console.error(
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. " +
'This can happen if a SSR-ed Client Component used:\n' +
'\n' +
"- A server/client branch `if (typeof window !== 'undefined')`.\n" +
"- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n" +
"- Date formatting in a user's locale which doesn't match the server.\n" +
'- External changing data without sending a snapshot of it along with the HTML.\n' +
'- Invalid HTML tag nesting.\n' +
'\n' +
'It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.\n' +
'\n' +
'%s%s',
'https://react.dev/link/hydration-mismatch',
diff,
);
});
}
}
}

常量

1.

备注

源码部分 78 - 94 行

// The deepest Fiber on the stack involved in a hydration context.
// This may have been an insertion or a hydration.
// 堆栈中涉及水合上下文的最深层 Fiber。
// 这可能是插入或水合操作。
let hydrationParentFiber: null | Fiber = null;
// 下一次水合实例
let nextHydratableInstance: null | HydratableInstance = null;
// 是否正在水合
let isHydrating: boolean = false;

// This flag allows for warning supression when we expect there to be mismatches
// due to earlier mismatches or a suspended fiber.
// 当我们预期会因为早期的不匹配或挂起的 fiber 而出现不匹配时,该标志允许抑制警告。
let didSuspendOrErrorDEV: boolean = false;

// Hydration differences found that haven't yet been logged.
// 发现了尚未记录的水合作用差异。
let hydrationDiffRootDEV: null | HydrationDiffNode = null;

// Hydration errors that were thrown inside this boundary
// 在此边界内抛出的水合错误
let hydrationErrors: Array<CapturedValue<mixed>> | null = null;

// 根或单例上下文
let rootOrSingletonContext = false;

变量

工具

1. 构建水合差异节点

// Builds a common ancestor tree from the root down for collecting diffs.
// 从根节点向下构建一个公共祖先树以收集差异。
function buildHydrationDiffNode(
fiber: Fiber,
distanceFromLeaf: number,
): HydrationDiffNode {
if (fiber.return === null) {
// We're at the root.
if (hydrationDiffRootDEV === null) {
hydrationDiffRootDEV = {
fiber: fiber,
children: [],
serverProps: undefined,
serverTail: [],
distanceFromLeaf: distanceFromLeaf,
};
} else if (hydrationDiffRootDEV.fiber !== fiber) {
throw new Error(
'Saw multiple hydration diff roots in a pass. This is a bug in React.',
);
} else if (hydrationDiffRootDEV.distanceFromLeaf > distanceFromLeaf) {
hydrationDiffRootDEV.distanceFromLeaf = distanceFromLeaf;
}
return hydrationDiffRootDEV;
}
// + 递归调用
const siblings = buildHydrationDiffNode(
fiber.return,
distanceFromLeaf + 1,
).children;
// The same node may already exist in the parent. Since we currently always render depth first
// and rerender if we suspend or terminate early, if a shared ancestor was added we should still
// be inside of that shared ancestor which means it was the last one to be added. If this changes
// we may have to scan the whole set.
//
// 相同的节点可能已经存在于父节点中。由于我们当前总是先按深度渲染
// 并且在挂起或提前终止时重新渲染,如果添加了共享的祖先节点,我们仍然
// 应该在该共享祖先节点内部,这意味着它是最后被添加的。如果情况发生变化,
// 我们可能需要扫描整个集合。
if (siblings.length > 0 && siblings[siblings.length - 1].fiber === fiber) {
const existing = siblings[siblings.length - 1];
if (existing.distanceFromLeaf > distanceFromLeaf) {
existing.distanceFromLeaf = distanceFromLeaf;
}
return existing;
}
const newNode: HydrationDiffNode = {
fiber: fiber,
children: [],
serverProps: undefined,
serverTail: [],
distanceFromLeaf: distanceFromLeaf,
};
siblings.push(newNode);
return newNode;
}

2. 警告未水合实例

备注
  • describeHydratableInstanceForDevWarnings() 由渲染平台实现
function warnNonHydratedInstance(
fiber: Fiber,
rejectedCandidate: null | HydratableInstance,
) {
if (__DEV__) {
if (didSuspendOrErrorDEV) {
// Inside a boundary that already suspended. We're currently rendering the
// siblings of a suspended node. The mismatch may be due to the missing
// data, so it's probably a false positive.
//
// 在已经挂起的边界内。我们当前正在渲染一个挂起节点的兄弟节点。
// 不匹配可能是由于数据缺失引起的,因此这很可能是误报。
return;
}

// Add this fiber to the diff tree.
// 将此 fiber 添加到差异树中。
// + 本文档实现
const diffNode = buildHydrationDiffNode(fiber, 0);
// We use null as a signal that there was no node to match.
// 我们使用 null 作为未找到匹配节点的信号。
diffNode.serverProps = null;
if (rejectedCandidate !== null) {
const description =
describeHydratableInstanceForDevWarnings(rejectedCandidate);
diffNode.serverTail.push(description);
}
}
}

3. 尝试水合实例

备注
  • canHydrateInstance() 由渲染平台实现
  • diffHydratedPropsForDevWarnings() 由渲染平台实现
  • getFirstHydratableChild() 由渲染平台实现
function tryHydrateInstance(
fiber: Fiber,
nextInstance: any,
hostContext: HostContext,
) {
// fiber is a HostComponent Fiber
// fiber 是一个宿主组件 Fiber
const instance = canHydrateInstance(
nextInstance,
fiber.type,
fiber.pendingProps,
rootOrSingletonContext,
);
if (instance !== null) {
fiber.stateNode = instance as Instance;

if (__DEV__) {
if (!didSuspendOrErrorDEV) {
const differences = diffHydratedPropsForDevWarnings(
instance,
fiber.type,
fiber.pendingProps,
hostContext,
);
if (differences !== null) {
// + 由本文档实现
const diffNode = buildHydrationDiffNode(fiber, 0);
diffNode.serverProps = differences;
}
}
}

hydrationParentFiber = fiber;
nextHydratableInstance = getFirstHydratableChild(instance);
rootOrSingletonContext = false;
return true;
}
return false;
}

4. 尝试水合文本

备注
  • canHydrateTextInstance() 由渲染平台实现
function tryHydrateText(fiber: Fiber, nextInstance: any) {
// fiber is a HostText Fiber
// fiber 是一个宿主组件 Fiber
const text = fiber.pendingProps;
const textInstance = canHydrateTextInstance(
nextInstance,
text,
rootOrSingletonContext,
);
if (textInstance !== null) {
fiber.stateNode = textInstance as TextInstance;
hydrationParentFiber = fiber;
// Text Instances don't have children so there's nothing to hydrate.
// 文本实例没有子节点,所以无需进行 hydration。
nextHydratableInstance = null;
return true;
}
return false;
}

5. 尝试水合活动(组件)

备注
function tryHydrateActivity(
fiber: Fiber,
nextInstance: any,
): null | ActivityInstance {
// fiber is a ActivityComponent Fiber
// fiber 是一个活动组件 Fiber
const activityInstance = canHydrateActivityInstance(
nextInstance,
rootOrSingletonContext,
);
if (activityInstance !== null) {
const activityState: ActivityState = {
dehydrated: activityInstance,
treeContext: getSuspendedTreeContext(),
retryLane: OffscreenLane,
hydrationErrors: null,
};
fiber.memoizedState = activityState;
// Store the dehydrated fragment as a child fiber.
// This simplifies the code for getHostSibling and deleting nodes,
// since it doesn't have to consider all Suspense boundaries and
// check if they're dehydrated ones or not.
//
// 将脱水的片段存储为子 Fiber。
// 这简化了 getHostSibling 和删除节点的代码,
// 因为它不必考虑所有的 Suspense 边界,也不必检查它们是否已脱水。
const dehydratedFragment =
createFiberFromDehydratedFragment(activityInstance);
dehydratedFragment.return = fiber;
fiber.child = dehydratedFragment;
hydrationParentFiber = fiber;
// While an Activity Instance does have children, we won't step into
// it during the first pass. Instead, we'll reenter it later.
//
// 虽然一个活动实例有子项,但在第一次遍历时我们不会进入它。
// 相反,我们稍后会重新进入它。
nextHydratableInstance = null;
}
return activityInstance;
}

6. 尝试水合挂起(组件)

备注
function tryHydrateSuspense(
fiber: Fiber,
nextInstance: any,
): null | SuspenseInstance {
// fiber is a SuspenseComponent Fiber
// fiber 是一个挂起组件 Fiber
const suspenseInstance = canHydrateSuspenseInstance(
nextInstance,
rootOrSingletonContext,
);
if (suspenseInstance !== null) {
const suspenseState: SuspenseState = {
dehydrated: suspenseInstance,
treeContext: getSuspendedTreeContext(),
retryLane: OffscreenLane,
hydrationErrors: null,
};
fiber.memoizedState = suspenseState;
// Store the dehydrated fragment as a child fiber.
// This simplifies the code for getHostSibling and deleting nodes,
// since it doesn't have to consider all Suspense boundaries and
// check if they're dehydrated ones or not.
//
// 将脱水的片段存储为子 Fiber。
// 这简化了 getHostSibling 和删除节点的代码,
// 因为它不必考虑所有的 Suspense 边界,也不必检查它们是否已脱水。
const dehydratedFragment =
createFiberFromDehydratedFragment(suspenseInstance);
dehydratedFragment.return = fiber;
fiber.child = dehydratedFragment;
hydrationParentFiber = fiber;
// While a Suspense Instance does have children, we won't step into
// it during the first pass. Instead, we'll reenter it later.
//
// 虽然 Suspense 实例有子节点,但在第一次遍历时我们不会进入它。
// 相反,我们会在稍后重新进入它。
nextHydratableInstance = null;
}
return suspenseInstance;
}

7. 在水合不匹配时抛出

备注
function throwOnHydrationMismatch(fiber: Fiber, fromText: boolean = false) {
let diff = '';
if (__DEV__) {
// Consume the diff root for this mismatch.
// Any other errors will get their own diffs.
const diffRoot = hydrationDiffRootDEV;
if (diffRoot !== null) {
hydrationDiffRootDEV = null;
diff = describeDiff(diffRoot);
}
}
const error = new Error(
`Hydration failed because the server rendered ${fromText ? 'text' : 'HTML'} didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:
` +
'\n' +
"- A server/client branch `if (typeof window !== 'undefined')`.\n" +
"- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n" +
"- Date formatting in a user's locale which doesn't match the server.\n" +
'- External changing data without sending a snapshot of it along with the HTML.\n' +
'- Invalid HTML tag nesting.\n' +
'\n' +
'It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.\n' +
'\n' +
'https://react.dev/link/hydration-mismatch' +
diff,
);
queueHydrationError(createCapturedValueAtFiber(error, fiber));
throw HydrationMismatchException;
}

8. 跳过脱水活动实例

备注
  • getNextHydratableInstanceAfterActivityInstance() 由渲染平台实现
function skipPastDehydratedActivityInstance(
fiber: Fiber,
): null | HydratableInstance {
const activityState: null | ActivityState = fiber.memoizedState;
const activityInstance: null | ActivityInstance =
activityState !== null ? activityState.dehydrated : null;

if (!activityInstance) {
throw new Error(
'Expected to have a hydrated suspense instance. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
}

return getNextHydratableInstanceAfterActivityInstance(activityInstance);
}

9. 跳过脱水挂起实例

备注
  • getNextHydratableInstanceAfterSuspenseInstance() 由渲染平台实现
function skipPastDehydratedSuspenseInstance(
fiber: Fiber,
): null | HydratableInstance {
if (!supportsHydration) {
throw new Error(
'Expected skipPastDehydratedSuspenseInstance() to never be called. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
}
const suspenseState: null | SuspenseState = fiber.memoizedState;
const suspenseInstance: null | SuspenseInstance =
suspenseState !== null ? suspenseState.dehydrated : null;

if (!suspenseInstance) {
throw new Error(
'Expected to have a hydrated suspense instance. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
}

return getNextHydratableInstanceAfterSuspenseInstance(suspenseInstance);
}

10. 返回到下一个宿主父级

function popToNextHostParent(fiber: Fiber): void {
hydrationParentFiber = fiber.return;
while (hydrationParentFiber) {
switch (hydrationParentFiber.tag) {
case HostComponent:
case ActivityComponent:
case SuspenseComponent:
rootOrSingletonContext = false;
return;
case HostSingleton:
case HostRoot:
rootOrSingletonContext = true;
return;
default:
hydrationParentFiber = hydrationParentFiber.return;
}
}
}

11. 在尾节点未水合时发出警告

备注
  • describeHydratableInstanceForDevWarnings() 由渲染平台实现
  • getNextHydratableSibling() 由渲染平台实现
function warnIfUnhydratedTailNodes(fiber: Fiber) {
if (__DEV__) {
let nextInstance = nextHydratableInstance;
while (nextInstance) {
// 本文档实现
const diffNode = buildHydrationDiffNode(fiber, 0);
const description =
describeHydratableInstanceForDevWarnings(nextInstance);
diffNode.serverTail.push(description);
if (description.type === 'Suspense') {
const suspenseInstance: SuspenseInstance = nextInstance as any;
nextInstance =
getNextHydratableInstanceAfterSuspenseInstance(suspenseInstance);
} else {
nextInstance = getNextHydratableSibling(nextInstance);
}
}
}
}