ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Gatsby 部分水合(Partial Hydration)完全指南:基于 React Server Components 的选择性交互架构

Gatsby 部分水合(Partial Hydration)完全指南:基于 React Server Components 的选择性交互架构 Gatsby 部分水合Partial Hydration完全指南基于 React Server Components 的选择性交互架构【免费下载链接】gatsbyReact-based framework with performance, scalability, and security built in.项目地址: https://gitcode.com/gh_mirrors/ga/gatsby本指南围绕 Gatsby 5 引入的 Partial Hydration部分水合功能展开系统讲解它如何借助 React Server Components让原本全站静态的页面只对真正需要交互的组件下发并执行 JavaScript从而显著改善 Time to Interactive可交互时间等前端性能指标。读完本文你将掌握 Partial Hydration 的概念模型、Gatsby 内部的 RSC 构建原理以及如何在真实项目中声明客户端组件、划定水合边界并规避已知限制。从全量水合到部分水合什么是水合HydrationGatsby 的核心设计之一是在构建期使用 React 的ReactDOMServer将页面渲染成静态 HTML发布到/public目录浏览器拿到这份 HTML 后再由客户端 JavaScript 为服务器渲染好的标记接续状态与交互能力——这个过程就是水合Hydration有时也写作 re-hydration二者等价。水合的本质是用客户端 JavaScript 为服务端渲染的 HTML 添加应用状态与交互性。传统 SSR/静态站点存在一个天然矛盾gatsby build产出的 HTML 立即可见First Contentful Paint 很快但事件监听器要等 JavaScript 下载、解析、执行完毕后才挂载。用户看到貌似可点击的按钮却无法真正点击这种UI 已渲染但尚未可交互的窗口期在业内被称为 uncanny valley恐怖谷效应直接拖累 Time to Interactive 指标。Gatsby 也据此在自己的水合概念指南中完整阐述了这一过程每次首访响应返回静态 HTML 与关联的 JS/CSS/图片随后 React 通过hydrateRoot()接管 DOM、挂载事件监听把站点变成完整的 React 应用此后的页面跳转则完全是 React 管理的 DOM 更新。全量水合的问题从 Gatsby 诞生到 4.x凡是 Gatsby 构建的站点在客户端都会做全量水合无论页面里有多少静态内容页头、页脚、纯文本段落浏览器都必须下载整套页面 JavaScript、由 React 为整棵组件树创建 VDOM 节点并逐一求值。结果是——为那些根本不需要交互的部分也白白传输并执行了 JavaScript。部分水合的思路Partial Hydration 的理念恰恰相反只对页面中孤岛式的交互区域下发并水合 JavaScript其余部分保持纯静态 HTML。下图直观对比了两种模式——左侧是全量水合整个浏览器窗口含静态内容都被标记为蓝色表示整页参与水合右侧是部分水合只有交互式画廊区域被标记。你可以把 Partial Hydration 理解为一种超级代码分割如果某个重型库只在服务端渲染阶段被用到就完全不必打包发给客户端。客户端 JavaScript 越少最直接的收益是 TTI可交互时间缩短同时也能缓解恐怖谷效应——让用户看到的 UI 与真正可交互的 UI 尽快一致。下图展示了水合在页面渲染时间线中的位置服务端返回 HTML 并完成绘制后JS 仍需到达 → 处理 → 水合才能让 UI 可交互这一整段等待正是部分水合要压缩或消除的部分。值得澄清的是部分水合与近年流行的 island architecture孤岛架构如 Astro 所推行最终效果相似——页面上出现一个个可独立水合的交互孤岛但实现路径截然不同。Gatsby 之所以选择 React Server Components 而非孤岛架构核心原因是让开发者基本延续原有的书写习惯详见下文为什么是 React Server Components。Gatsby 中部分水合的工作原理Gatsby 的 Partial Hydration 建立在 React Server ComponentsRSC之上详细设计可参考 React 官方的 Server Components RFC外部资料仅作背景参考。默认全部是服务端组件开启功能后Gatsby 从顶层页面如src/pages下的页面或通过createPageAPI 创建的页面开始默认把所有组件标记为服务端组件。除非用use client指令显式声明否则组件不会向客户端下发任何 JavaScript——这是开箱即用的性能提升的来源。页面请求从 JS 变为 RSC 描述文件在部分水合模式下浏览器不再像传统模式那样请求页面组件的 JavaScript 文件而是请求page-data-rsc.json。这个 JSON 文件是 UI 的描述其中客户端组件以 bundle 引用reference的形式出现浏览器再依据引用去加载组件真正的代码。这也解释了为什么 server 组件向 client 组件传递的 props 必须可序列化——它们会被写入 JSON 文件函数、回调等无法序列化的值自然无法传递。use client 指令与组件边界use client是 React 的服务端模块约定中的关键指令作为文件的第一行代码出现。它声明了服务端与客户端组件之间的边界一个模块只要标记了use client它及其依赖的模块就整体进入客户端包其渲染出的 HTML 会在客户端被水合。服务端组件与客户端组件可以在应用中混用React 会在幕后把它们合并到同一棵组件树里如下图的组件树所示服务端组件可以包含服务端与客户端组件客户端组件也可以嵌套服务端与客户端组件。构建期的清单manifest生成在构建过程中Gatsby 会扫描所有组件生成一张客户端组件清单记录每个客户端组件及其对应的 chunk再依据该清单产出 RSC 输出。这一点可以直接在仓库源码中得到印证PartialHydrationPlugin 通过 webpack 的 parser hook 检查 AST 中是否存在use client指令statement.directive use client命中后把module.buildInfo.rsc置为true以标记客户端模块随后在processAssets阶段调用_generateManifest从compilation.moduleGraph与chunkGraph中收集各客户端模块的导出与 chunk 编号生成映射所有客户端组件及其独立 chunk 的清单文件并在增量构建时合并上一次的清单以保留缓存this._previousManifest。与清单配合的还有 partial-hydration-reference-loader它对包含use client的模块进行解析把每个具名导出与默认导出改写为$$typeof: Symbol.for(react.module.reference)形式的模块引用对象并附带filepath与导出名——这正是 RSC 输出中客户端组件以 bundle 引用形式出现的底层实现也是page-data-rsc.json能够以最小体积描述 UI 的原因。为什么是 React Server Components而非孤岛架构如果没接触过 RSC建议先观看 React 官方介绍 Server Components 的演讲或阅读 RFC外部资料。Gatsby 选择 RSC 实现部分水合而没有倒向孤岛架构理由可以概括为让你大体上继续用习惯的方式写应用。在孤岛架构的世界里开发者要分别创作一个个孤岛层每个孤岛拥有独立的上下文context与 React 树。这意味着无法简单地在孤岛间共享 context或让 React 事件在孤岛间向上/向下冒泡必须自行编写连接逻辑思维方式需要转变——创建应用时要把静态壳与交互孤岛当作不同的实体来组织孤岛无法渲染整页因而不适用于 SPA会导致页面间导航更迟缓。而 React Server Components 让应用的大部分仍保持原来的写法只需遵循若干约束而不是一场彻底的范式转换。其附带收益包括对 bundle 体积零影响服务端组件代码根本不会进入客户端 bundle与客户端组件无缝集成二者在同一组件树中共存、由 React 统一协调子树/组件级更新并保留客户端状态服务端返回的 RSC 流可以只更新局部子树同时保持客户端组件的既有状态。实战在 Gatsby 5 中启用部分水合概念之外Gatsby 还提供了对应的操作指南以下步骤、代码与该指南保持一致。前置条件一个基于gatsby5.0.0或更高版本的项目可从快速开始起步安装reactexperimental与react-domexperimentalnpm install --save-exact reactexperimental react-domexperimental --legacy-peer-deps在gatsby-config.js中开启PARTIAL_HYDRATION标志module.exports { flags: { PARTIAL_HYDRATION: true } }关于该标志位源码提供了更多可验证的细节flags.ts 中定义标志名PARTIAL_HYDRATION对应环境变量GATSBY_PARTIAL_HYDRATIONtelemetry 标识为PartialHydrationcommand: build——即该功能只在构建类命令下生效experimental: true——当前处于实验阶段testFitness校验仅当GATSBY_MAJOR 5且本机 React 满足18.0.0或^0.0.0的 experimental 版本时才可启用不满足时会给出明确提示Partial hydration requires React 18 to work.服务端组件默认行为启用后所有组件默认是服务端组件。gatsby build产出的 HTML 在客户端不依赖任何 JavaScript性能提升开箱即得Gatsby 从顶层页面src/pages或createPage开始生成服务端组件。只有在组件确实需要交互时才需要显式标记为客户端组件。客户端组件use client客户端组件的 HTML 会在客户端被水合——即用客户端 JavaScript 为服务端渲染的 HTML 添加状态与交互。声明方式是在文件第一行写入use client指令use client import * as React from react const Joke () { const [isShown, show] React.useReducer(() true, false) return ( main button onClick{show}Show me a joke/button {isShown pWhy couldn’t the React component understand the joke? Because it didn’t get the context./p} /main ) } export default Joke何时应使用客户端组件官方建议默认用服务端组件只对确有必要的地方选择性地声明客户端组件。典型的客户端组件使用场景包括需要交互与事件监听onClick()、onChange()等需要状态与生命周期useState()、useEffect()等需要访问浏览器专有 API如读取window上的属性React 类组件。组件边界与组件树优化的最佳实践不必给每个交互组件都加指令use client只需加在被服务端组件导入的组件上由此在服务端与客户端之间划出边界客户端组件内部再导入的其他客户端组件无需重复声明。例如src/pages/index.jsx导入了SocialMedia而SocialMedia又导入Instagram与Twitter三者都用到了useEffect()——由于只有SocialMedia是被服务端组件导入的客户端组件只需在SocialMedia上加use client即可。把客户端组件推向组件树叶子组织组件结构时应尽可能把客户端组件放到组件树的叶子节点以最小化下发到客户端的 JavaScript。假如共享布局组件里有一个展示最新推文的交互式页脚不要给整个布局加use client而应把页脚拆成独立组件只标记它use client import * as React from react const Footer () { React.useEffect(() { // do fetching stuff }) return ( footerMy Tweets/footer ) } export default Footerimport * as React from react // Footer is a client component import Footer from ./footer const Layout ({ children }) ( main{children}/main Footer / / ) export default Layout服务端组件不能导入客户端组件但可以作为 children 传入服务端组件不能被导入客户端组件但可以以childrenprop 的形式传入客户端组件让 React 同时实例化二者use client import * as React from react export const MyClientComponent ({ children }) ( div pRe-Hydrated on the client/p {children} /div )import * as React from react import { MyServerComponent } from ../components/my-server-component import { MyClientComponent } from ../components/my-client-component const Page () ( MyClientComponent MyServerComponent / /MyClientComponent ) export default Pageprops 必须可序列化服务端组件向客户端组件传 props 的方式与往常基本一致但必须可序列化——函数、回调等无法写入 JSON 的值不能传递// OK const Page () ClientComponent colorrebeccapurple / // ⚠️ Doesnt work const Page () ( ClientComponent onClick{() console.log(Hello World)} / )当前限制务必知悉使用 Partial Hydration 前请确认以下限制必须使用 React 的experimental 发布版官方不建议在生产环境使用React 生态中大量包尚未适配 React Server Components例如 CSS-in-JS 解决方案Partial Hydration 仅在gatsby build与gatsby serve下生效不适用于gatsby develop——这与源码中标志位command: build的定义完全一致。深入阅读概念篇React 水合Hydration概念指南——了解全量水合与hydrateRoot()的工作原理操作篇使用 Partial Hydration 实战指南——包含完整的启用步骤与 FAQ源码篇标志位定义、PartialHydrationPlugin 与 partial-hydration-reference-loader——深入 RSC 清单生成与模块引用改写的实现细节【免费下载链接】gatsbyReact-based framework with performance, scalability, and security built in.项目地址: https://gitcode.com/gh_mirrors/ga/gatsby创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表