资讯动态

React Router Framework Mode 深入解析:<Scripts> 组件如何注入客户端运行时

发布时间:2026/9/7 4:11:20 来源:尧图企业网站定制
React Router Framework Mode 深入解析 组件如何注入客户端运行时【免费下载链接】react-routerDeclarative routing for React项目地址: https://gitcode.com/GitHub_Trending/re/react-router在 React Router 的 Framework Mode 中服务端渲染出完整的 HTML 只是第一步要让页面具备客户端路由、数据加载与 Suspense 流式更新能力还需要把「客户端运行时」注入到文档中——这正是Scripts /组件的职责。本文基于 Scripts API 文档 与 核心源码 展开讲清楚Scripts /应该放在哪里、能传哪些 props、可以省略的无 JavaScript 降级策略以及它在源码层面实际渲染出的两类script标签、modulepreload链接、SRI importmap 与 nonce 回退机制帮助你既会用又能排查其产物。组件定位渲染应用客户端运行时Scripts /是 Framework Mode 下的文档级组件标记为[MODES: framework]官方文档给出的定义是Renders the client runtime of your app. It should be rendered inside thebodyof the document.即它负责渲染你应用的客户端运行时且应当渲染在文档的body内部。文档同时给出了一个关键特性——在服务端渲染场景下你可以直接省略Scripts /此时应用会退化为一个传统的无 JavaScript 网页仅依赖 HTML 与浏览器原生行为链接跳转、表单提交工作If server rendering, you can omitScripts/and the app will work as a traditional web app without JavaScript, relying solely on HTML and browser behaviors.最简用法即官方文档中的示例将Scripts /放在根路由的body中import { Scripts } from react-router; export default function Root() { return ( html head / body Scripts / /body /html ); }完整用法在 app/root.tsx 中放置文档级组件根路由文档 说明了app/root.tsx是 Framework Mode 中唯一必需的根路由负责渲染整个html文档因此Scripts /这类「文档级组件」应当且只需在根路由中出现一次。文档推荐的完整写法是import { Outlet, Scripts, ScrollRestoration, } from react-router; export default function App() { return ( html langen head meta charSetutf-8 / meta nameviewport contentwidthdevice-width, initial-scale1 / /head body {/* Child routes render here */} Outlet / {/* Manages scroll position for client-side transitions */} ScrollRestoration / {/* Script tags go here */} Scripts / /body /html ); }如果你的根路由使用了Layout导出用于在根组件 /HydrateFallback/ErrorBoundary之间复用应用外壳避免 FOUCScripts /应放在Layout的body中位于{children}之后。仓库内的 playground 示例 就是这种结构的真实用例// playground/framework/app/root.tsx export function Layout({ children }: { children: React.ReactNode }) { return ( html langen head meta charSetutf-8 / meta nameviewport contentwidthdevice-width, initial-scale1 / Meta / Links / /head body {children} ScrollRestoration / Scripts / /body /html ); } export default function App() { return Outlet /; }注意playground中还演示了一个细节导航Link上带prefetchintent与Scripts /注入的客户端运行时配合后路由模块的预取modulepreload由运行时管理会在用户意图导航时提前发生。函数签名与 Props官方文档给出的签名是function Scripts(scriptProps: ScriptsProps): React.JSX.Element | nullscriptProps用于向渲染出的script标签上批量展开额外属性官方文档明确列举的典型用途是crossOrigin与nonce。对照 源码中ScriptsProps的类型定义其结构为export type ScriptsProps Omit React.HTMLPropsHTMLScriptElement, | async | children | dangerouslySetInnerHTML | defer | noModule | src | suppressHydrationWarning | type { /** * A nonce attribute to render on the script element. * If not provided in Framework Mode, it will default to any * ServerRouter nonce prop. */ nonce?: string | undefined; };由此可以归纳出Scripts /的 props 规则属性是否可传说明nonce可传渲染到每个script/ 相关link上用于基于 nonce 的 CSP不传时回退到ServerRouter noncecrossOrigin可传静态资源托管在与应用不同域名如 CDN时使用源码中同样会应用到modulepreload链接上async/defer/noModule/src/type不可传由 React Router 内部统一管理类型上被Omit掉源码 JSDoc 明确说明这些属性是 managed by React Router internallychildren/dangerouslySetInnerHTML不可传脚本内容内联上下文、路由模块导入由运行时内部生成suppressHydrationWarning不可传内部生成的脚本标签已自行处理水合警告其余script合法属性可传通过...scriptProps展开到每个script上源码剖析Scripts /实际渲染了什么从 Scripts 函数实现 看这个组件在「已水合」与「RSC 上下文」之外的常规场景下会返回一组片段类型上因此可以是null。核心渲染产物分为四类1. 内联上下文脚本 路由模块 module 脚本组件内部通过React.useMemo且故意使用空依赖数组只为水合这一次渲染生成两个script标签上下文脚本无type即经典内联脚本写入window.__reactRouterContext ${serverHandoffString}即服务端交接数据并额外初始化一个ReadableStreamwindow.__reactRouterContext.stream new ReadableStream({ start(controller) { window.__reactRouterContext.streamController controller; } }).pipeThrough(new TextEncoderStream());这段stream是为 Suspense 流式传输准备的控制器通道服务端后续流式推送的内容通过它送达客户端。在静态渲染staticContext存在时才写入serverHandoffString否则为空脚本。路由模块脚本typemodule async在静态渲染场景下生成一批import语句。从 源码的路由模块脚本拼装逻辑 看它按当前matches逐条路由导入 manifest 中的clientActionModule、clientLoaderModule、clientMiddlewareModule、hydrateFallbackModule与module五个可选块非分块模式下还会import完整manifest.url最后将各块合并为window.__reactRouterRouteModules并import()入口模块manifest.entry.module。也就是说这个脚本承担了「把服务端渲染过的路由模块在客户端重新挂载」的引导职责。两个脚本标签都会展开{...scriptProps}即你传入的nonce、crossOrigin等并各自带suppressHydrationWarning。2. modulepreload 链接水合完成前组件还会渲染一批link relmodulepreload非「fog of war」模式下预加载完整路由 manifestmanifest.url始终预加载入口模块manifest.entry.module预加载manifest.entry.imports与当前匹配路由模块的链接去重后包含HydrateFallback所需模块。每个链接都会带上你传入的crossOrigin、SRIintegrity与nonce。一旦isHydrated变为true组件通过useEffect调用导出的setIsHydrated()这些 preload 列表清空组件返回null——源码注释解释了原因初始脚本只为首次水合渲染一次之后保持页面初始加载时的脚本状态避免客户端更新时重复注入。3. SRI importmap当构建产物启用了 Subresource Integritymanifest.sri为对象时组件会额外渲染一个typeimportmap的脚本内容为{ integrity: sri }供浏览器对动态import()的模块做完整性校验对应的modulepreload链接也会带上各自的integrity属性。4. RSC 场景下的 no-op在 RSC 路由上下文中组件会跳过所有脚本生成initialScripts直接为null最终返回null并通过warnOnce提示TheScripts /element is a no-op when using RSC and can be safely removed.因此如果你从 Framework Mode 迁移到 RSC 模式根路由里的Scripts /可以直接移除。CSP nonce 支持Scripts /内部会渲染内联脚本因此在启用基于 nonce 的 Content-Security-Policy 时这些标签必须携带nonce。仓库的 Security 指南 给出了 Framework Mode无 RSC场景的标准做法在entry.server.tsx中把 nonce 传给ServerRouter nonce——它会通过 React Context 代理下去作为Scripts /、ScrollRestoration /等「nonce 感知组件」在未显式指定nonce时的默认值把同一个 nonce 传给renderToPipeableStream/renderToReadableStream的nonce选项。源码中这一回退逻辑清晰可见Scripts 函数开头 会先从useFrameworkContext()取出nonce: contextNonce当scriptProps.nonce为空时用上下文中的值补齐// Fall back to the nonce provided via FrameworkContext (e.g. from // ServerRouter nonce) when one isnt passed explicitly. This ensures the // inline hydration scripts carry a nonce even when Scripts is rendered // internally without props (such as in the default HydrateFallback). if (scriptProps.nonce null contextNonce) { scriptProps { ...scriptProps, nonce: contextNonce }; }组件自身传入的nonce会覆盖ServerRouter的值这一点在 ServerRouter 文档中也有对应说明。省略Scripts /无 JavaScript 降级与可访问性如前所述服务端渲染时省略Scripts /后应用就是一个普通的多页网站锚点跳转、浏览器表单行为接管React Router 不会拦截导航。可访问性指南 也基于这一点给出建议若用户禁用 JavaScript你的 React Router 应用「by default」应当以传统多页方式可用反过来当你渲染了Scripts /React Router 会接管路由并阻止浏览器默认行为此时需要自行关注焦点管理路由切换后哪个元素获得焦点与 live-region 播报路由变化、过渡状态通知这是键盘与读屏用户可用的关键。这是一个值得显式决策的取舍保留Scripts /获得 SPA 级交互与数据能力省略它则换取对无 JS 环境最彻底的兼容。小结Scripts /是 Framework Mode 根路由body中的客户端运行时注入点签名function Scripts(scriptProps: ScriptsProps): React.JSX.Element | null可传nonce、crossOrigin等属性但async、defer、noModule、src、type、children、dangerouslySetInnerHTML、suppressHydrationWarning由 React Router 内部托管、不可覆盖实际渲染物 内联window.__reactRouterContext交接脚本含 Suspense 流控制器 路由模块 module 脚本 一组modulepreload链接 可选 SRI importmap水合完成后组件不再输出CSP 场景下通过ServerRouter nonce统一注入 nonce 最省心组件级nonce可覆盖服务端渲染时省略Scripts /即可获得无 JavaScript 的传统网页体验但此时需自行保障可访问性焦点、播报。进一步阅读可参考 Scripts API 文档、ServerRouter 文档、root.tsx 约定文档 与实现源码 packages/react-router/lib/dom/ssr/components.tsx。【免费下载链接】react-routerDeclarative routing for React项目地址: https://gitcode.com/GitHub_Trending/re/react-router创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价