-
Notifications
You must be signed in to change notification settings - Fork 4
Description
快速预览
react hydrate 是指,在 SSR 模式下渲染出 html 字符串发送到浏览器渲染之后,绑定监听事件以及将 react Fiber 树构建出来的过程,也就是所谓的「水合」,或者叫做「注水」。
一个简单的例子🌰
import { createElement } from "react";
import {
renderToString,
} from "react-dom/server";
import http from "http";
const hello = createElement(
"div",
{
onClick: () => {
console.log("hello");
},
},
"Hello World"
)
const server = http.createServer((req, res) => {
const html = renderToString(
hello
);
res.end(`
<!DOCTYPE html>
<html lang="en">
<head>
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
</head>
<body>
<div id="root">${html}</div>
<script>
const { createElement } = window.React;
const { hydrateRoot, createRoot } = window.ReactDOM;
const root = hydrateRoot(
document.getElementById('root'),
createElement(
"div",
{
onClick: () => {
console.log("hello");
},
},
"Hello World"
),
);
</script>
</body>
</html>`);
});
server.listen(3001);大致过程:通过 renderToString 等方法,将 React 组件代码处理成 HTML(Fiber 树 => HTML,去掉所有绑定事件,只留 HTML 信息),返回浏览器或者搜索引擎,实现 SSR 的效果。客户端 React 也不需要再一次处理 DOM 元素,只需要绑定的事件,处理 Fiber 树等即可。一举两得。
重点就在下面的代码里面,调用 hydrateRoot 时第二个参数,也就是把 React 组件重新「执行」一下。这里的 React 组件要和服务端 SSR 时放入的组件一致。
<script>
const { createElement } = window.React;
const { hydrateRoot, createRoot } = window.ReactDOM;
const root = hydrateRoot(
document.getElementById('root'),
createElement(
"div",
{
onClick: () => {
console.log("hello");
},
},
"Hello World"
),
);
</script>下面进入代码分析阶段,回答 2 个问题:
- 事件什么时候绑定
- Fiber 树如何被构建出来,HostComponent 的 fiber.stateNode 何时被赋值?
事件什么时候绑定
hydrateRoot 函数内部会调用 listenToAllSupportedEvents(container),这里的 container 就是 DOM 容器节点 。listenToAllSupportedEvents 函数就是处理委托事件的开始入口,具体实现可以看这篇文章。
Fiber 树构建
承接上段,hydrateRoot 函数内部会执行如下创建逻辑,进入 Fiber 树构造。最大的区别在于,跳过 DOM 树的创建和增加。
createHydrationContainer(createFiberRoot: FiberRootNode, createHostRootFiber)
createFiberRoot的第 3 个参数被赋值为 true,最终影响该数据结构
const initialState: RootState = {
element: initialChildren,
isDehydrated: hydrate,
cache: (null: any), // not enabled yet
transitions: null,
};
uninitializedFiber.memoizedState = initialState;scheduleInitialHydrationOnRoot,和scheduleUpdateOnFiber相比,代码更加简单,标记 lanes,就直接进入ensureRootIsScheduled阶段
export function scheduleInitialHydrationOnRoot(
root: FiberRoot,
lane: Lane,
eventTime: number,
) {
const current = root.current;
current.lanes = lane;
markRootUpdated(root, lane, eventTime);
ensureRootIsScheduled(root, eventTime);
}以优先级 16 开启更新
开始更新时,使用默认优先级 16,React18 版本下会进入并发,但不会分片。(16 是在 BlockingLane 范围内)
Render 阶段
updateHostRoot
在 render 阶段的 beginWork 阶段处理 HostRoot 的 updateHostRoot 函数内会对 isDehydrated 判断。如果是真值,会更新 updateQueue.baseState 和 workInProgress.memoizedState
// Flip isDehydrated to false to indicate that when this render
// finishes, the root will no longer be dehydrated.
const overrideState: RootState = {
element: nextChildren,
isDehydrated: false,
cache: nextState.cache,
transitions: nextState.transitions,
};
const updateQueue: UpdateQueue<RootState> = (workInProgress.updateQueue: any);
// `baseState` can always be the last state because the root doesn't
// have reducer functions so it doesn't need rebasing.
updateQueue.baseState = overrideState;
workInProgress.memoizedState = overrideState;以及调用 enterHydrationState 函数:获取第一个 DOM 节点并赋值全局变量 nextHydratableInstance。
enterHydrationState(workInProgress);
const parentInstance: Container = fiber.stateNode.containerInfo;
nextHydratableInstance = getFirstHydratableChildWithinContainer(
parentInstance,
);updateHostComponent
在 render 阶段的 beginWork 阶段处理 HostComponent 的 updateHostComponent 函数,会将已经存在的 DOM 实例和子 fiber 进行关联,也就是赋值 fiber.stateNode,通过 tryToClaimNextHydratableInstance 完成,tryHydrate 函数完成具体的赋值 stateNode 操作。
在 render 阶段的 completeOfWork 内处理 HostComponent 分支时,下面的语句返回真值,进入「水合」处理,判断当前节点是否存在更新
var _wasHydrated = popHydrationState(workInProgress);
if (_wasHydrated) {
if (prepareToHydrateHostInstance(workInProgress, rootContainerInstance, currentHostContext)) {
markUpdate(workInProgress);
}
} else {
...
}Commit 阶段
HostRoot
根据 HostRoot 的 memoizedState.isDehydrated 为 true,会进入 commitHydratedContainer 逻辑,该逻辑会处理 queuedDiscreteEvents。
后续
处理所有副作用和生命周期,和正常的 Commit 阶段类似,不再赘述。
以上