- 前端
【免费下载链接】rax
🐰 Rax is a progressive framework for building universal application. https://rax.js.org
本文围绕 Rax 生态中的
rax-document包展开,讲解如何用声明式 JSX 组件搭建页面 Document(<html>/<head>/<body>骨架)。你将掌握Root、Data、Style、Script、App五个核心组件的职责与用法,理解 SSR 场景下初始 HTML 与初始数据如何注入页面,以及多页应用(MPA)中如何通过App组件按路由注入pageInfo,并配合仓库源码与实际示例获得可直接落地的实战方案。
一、rax-document 是什么
rax-document是 Rax 框架体系中用于**构建 Document(HTML 文档骨架)**的组件集合。它的定位非常聚焦:把传统 HTML 模板中html/head/body的结构、样式表与脚本的注入、SSR 初始数据的挂载等重复性工作,抽象成一组声明式 JSX 组件。
在 Rax 应用中,src/document/index.jsx是一个特殊文件,它导出一个默认的Document组件,用来描述整张页面的外壳。仓库中所有示例项目(如 app-lifecycle、page-lifecycle-with-router、with-miniapp-native-page 等)都遵循同一套写法:从rax-document引入Root, Style, Script,拼装出页面骨架并导出。
从 package.json 可以看到,该包将rax@^1.0.0声明为peerDependencies,即它本身不捆绑渲染内核,而是运行在 Rax 的虚拟 DOM 之上,组件内直接使用createElement创建元素。
二、快速开始:最小可运行的 Document 组件
rax-document的典型使用方式是编写一个函数组件,返回标准的 HTML 结构。README 给出的完整示例如下(注意这段代码同时覆盖了 SSR 场景的初始数据注入):
import { createElement } from 'rax'; import { Root, Data, Style, Script } from 'rax-document'; export default () => { return ( <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1"/> <title>ssr-document-demo</title> <Style /> </head> <body> {/* root container */} <Root /> {/* initial data from server side */} <Data /> <Script /> </body> </html> ); }这段代码的要点:
<Style />放在<head>中,负责输出页面的样式表link标签;<Root />放在<body>中,作为页面根容器;<Data />紧跟根容器,在 SSR 项目里输出服务端下发的初始数据;<Script />放在<body>末尾,输出页面脚本标签。
值得注意的是,这里的html、head、body、meta、title都是普通 JSX 标签,说明rax-document允许(并且鼓励)在 Rax 组件树中直接书写文档级标签,再由渲染器输出到真实 HTML。
三、五个核心组件逐一拆解
rax-document一共导出六个组件,其中五个在 README 中做了说明。它们的实现全部位于 src/index.js,下面结合源码讲解各自职责。
3.1 Root:页面根容器
Root是页面的根容器节点。官方文档的定义是:
Root container for page. In normal Web app, it will be an empty node. In SSR project, the initial HTML will be output in this node.
在普通 Web 应用中它只是一个空节点;在 SSR 项目中,服务端渲染出来的初始 HTML 会被输出到这个节点中。源码实现如下:
function Root(props, context) { const { __initialHtml } = context; // comment node for ssr parse root node position if (process.env.__IS_SERVER__) { return [ { __html: '<!--__BEFORE_ROOT__-->' }, <div id="root" key="root" dangerouslySetInnerHTML={{ __html: __initialHtml || '' }} />, { __html: '<!--__AFTER_ROOT__-->' } ]; } return <div id="root" key="root" dangerouslySetInnerHTML={{ __html: __initialHtml || '' }} />; }从源码结构可以推断几个关键事实:
- 节点 id 固定为
root:<div id="root">是约定好的挂载点,客户端的rax-app等运行时会基于该节点完成水合(hydrate)或接管渲染; - SSR 与非 SSR 的差异:当
process.env.__IS_SERVER__为真时,Root会在#root前后额外输出<!--__BEFORE_ROOT__-->与<!--__AFTER_ROOT__-->两个 HTML 注释节点,用于让服务端解析器定位根节点的确切位置; - 初始 HTML 通过 context 注入:
__initialHtml来自渲染上下文(context),在客户端渲染时通常为空字符串,因此输出一个空的#root,等待运行时挂载。
3.2 Data:SSR 初始数据注入
Data只在 SSR 项目中需要,作用是将服务端计算好的初始数据挂到window上。源码:
function Data(props, context) { const { __initialData } = context; if (__initialData) { return <script>// Named by role rather than implementationm, so component name are `Style` rather than `Styles`. function Style(props, context) { const { __styles = [] } = context; return __styles.map((src, index) => <link {...props} rel="stylesheet" href={src} key={`style_${index}`} />); }源码揭示的实现细节:
- 样式资源从 context 的
__styles数组中读取,每个地址对应一个<link rel="stylesheet" href="...">; props会被透传到link标签上,意味着你可以给<Style />传任意 HTML 属性(如media、data-*等)来定制输出;- 组件名为
Style(单数)而非Styles(复数),源码注释明确说明这是按“职责”而非“实现”命名——它代表一个样式注入插槽,而不是一个样式集合; - 自动分析的机制意味着:只要你的页面组件中引入了 CSS(如示例中常见的
import './index.css'),构建阶段就会把产物地址填入__styles,<Style />处便会自动输出对应的link标签。
3.4 Script:自动分析的脚本标签
Script输出页面脚本。官方文档:
Scripts for page. Automatic analyzed from your project.
源码:
function Script(props, context) { const { __scripts = [] } = context; // props such as type can be passed to script tag // script default crossorigin value is anonymous return __scripts.map( (src, index) => <script crossorigin="anonymous" {...props} src={src} key={`script_${index}`}> {/* self-closing script element will not work in HTML */} </script> ); }实现要点:
- 脚本资源从 context 的
__scripts数组读取,每个地址输出一个<script src="...">; - 默认带上
crossorigin="anonymous",保证跨域加载脚本时不携带凭据,符合现代 Web 安全实践;该默认值可以被传入的props覆盖; props透传:例如需要输出type="module"的脚本时,直接写<Script type="module" />即可;- 源码注释特别指出一个 HTML 细节:自闭合的
<script />在 HTML 中不会生效,因此组件刻意输出成带内容的成对标签(内容为空),确保浏览器正确解析。
3.5 App:多页应用的 pageInfo 提供者
App组件服务于多页应用(Multi Page Application)场景,官方描述为:
Provide
pageInfofor Multi Page Application.
它的工作方式是接收app.json配置,根据当前页面路径查找到对应页面的信息,并通过render props(函数作为子元素)把pageInfo传给内部结构。README 中的完整示例:
import { createElement } from 'rax'; import { Root, Style, Script, App} from 'rax-document'; import appJSON from '../app.json'; function Document() { return ( <App config={appJSON}> { pageInfo => <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,viewport-fit=cover"/> <title>{pageInfo.title}</title> <Style /> </head> <body> {/* root container */} <Root /> <Script /> </body> </html> } </App> ); } export default Document;对比上一节的用法,这里<App config={appJSON}>包裹了一个函数子元素,函数接收pageInfo并返回完整的 HTML 结构,<title>{pageInfo.title}</title>说明页面标题可以直接取自当前路由配置。viewport也扩展为移动端常用的完整配置(含maximum-scale、user-scalable=no、viewport-fit=cover等,适配 WebView 与刘海屏场景)。
App的源码逻辑如下:
function App(props, context) { const config = props.config || {}; const routes = config.routes || []; const pagePath = context.__pagePath; const currentPageInfo = routes.find((route) => { return route.path == pagePath; }); const consumer = Array.isArray(props.children) ? props.children[0] : props.children; if (typeof consumer === 'function') { return consumer(currentPageInfo); } return props.children; }从源码可以推断其工作机制:
config即app.json的解析结果,核心是其中的routes数组;- 当前页面路径
__pagePath由渲染上下文(context)提供; - 组件用
route.path == pagePath在路由表中查找当前页面的配置项,找到的整个路由对象就是pageInfo; - 如果子元素是函数,则调用它并把
pageInfo传入(render props 模式);否则原样返回子元素。
作为对照,可以查看 page-lifecycle-with-router 示例的 app.json:其中routes数组的每一项包含path与source(如{ "path": "/", "source": "pages/Home/index" }),此外还有window(标题、下拉刷新等)与tabBar配置。App组件正是从这样的路由表中提取title等字段来填充pageInfo的。
3.6 未在 README 中详述的 Manifest
除 README 说明的五个组件外,src/index.js 还导出了Manifest组件:当 context 中存在__manifests数组时,它根据__pagePath查找匹配项,若有数据则输出<script type="application/manifest+json">,用于声明 Web App Manifest(PWA 相关)。该组件可作为进阶能力了解,其调用方式与Data类似,同样依赖渲染上下文注入数据。
四、在真实项目中的使用位置
rax-document的组件并不是在页面组件里直接使用,而是集中用于src/document/index.jsx。仓库中几乎每个示例项目都包含这个文件,例如:
- examples/app-lifecycle/src/document/index.jsx —— 基础应用骨架;
- examples/page-lifecycle-with-router/src/document/index.jsx —— 带路由的多页应用骨架;
- examples/event-handler/src/document/index.jsx —— 事件处理示例骨架;
- examples/get-element/src/document/index.jsx —— 元素获取示例骨架;
- examples/with-miniapp-native-page/src/document/index.jsx —— 混入小程序原生页面的示例骨架;
- examples/with-miniapp-plugin-component/src/document/index.jsx —— 使用小程序插件组件的示例骨架;
- examples/with-miniapp-native-custom-component/src/document/index.jsx —— 使用小程序原生自定义组件的示例骨架;
- examples/use-rax-compiled-component-in-runtime-miniapp/src/document/index.jsx —— 运行时小程序中复用编译组件的示例骨架。
这些文件内容高度一致,均以Root, Style, Script为核心(Data仅 SSR 场景需要),并默认采用面向移动端优化的 viewport 配置:
import { createElement } from 'rax'; import { Root, Style, Script} from 'rax-document'; function Document() { return ( <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,viewport-fit=cover" /> <title>rax-materials-basic-app</title> <Style /> </head> <body> {/* root container */} <Root /> <Script /> </body> </html> ); } export default Document;这说明一个事实:在 Rax 的标准工程结构里,document目录与pages、components平级(见 src/document 所在目录),Document组件默认导出后由构建与运行时统一消费,页面组件本身不需要关心 Document 的拼装。
五、底层机制:context 注入与渲染流程
理解rax-document的关键在于它依赖的渲染上下文(context)。从源码可见,Root、Data、Style、Script、Manifest、App都会从context中解构出以下字段:
| 组件 | 读取的 context 字段 | 作用 |
|---|---|---|
Root | __initialHtml | SSR 初始 HTML,注入#root节点 |
Data | __initialData | SSR 初始数据,输出window.__INITIAL_DATA__ |
Style | __styles | 自动分析的样式资源地址数组 |
Script | __scripts | 自动分析的脚本资源地址数组 |
Manifest | __manifests、__pagePath | Web App Manifest 数据与当前页面路径 |
App | __pagePath | 当前页面路径,用于在路由表中定位 pageInfo |
这些__前缀字段由服务端渲染器(如rax-server-renderer)在渲染 Document 时注入,因此:
- 服务端渲染(SSR):渲染器把编译产物分析出的
__styles、__scripts,以及页面渲染得到的__initialHtml、__initialData写入 context,rax-document组件据此输出完整的 HTML 骨架; - 客户端渲染(CSR):context 中这些字段通常为空或缺省(
__styles = []、__initialHtml || ''),组件输出空根节点等待客户端挂载,Data直接返回null。
这也是为什么文档中反复强调Data是“Required only in SSR project”,而Root在普通 Web 应用中“will be an empty node”——它们的差异正是由渲染上下文在不同场景下的注入内容决定的。
六、组件选型速查与注意事项
| 场景 | 推荐组合 | 说明 |
|---|---|---|
| 普通 Web 应用 | Root+Style+Script | 空#root等待运行时挂载,样式脚本自动注入 |
| SSR 应用 | Root+Data+Style+Script | 初始 HTML 与初始数据随首屏输出,Data必须放在 body 内 |
| 多页应用(MPA) | App+Root+Style+Script | 用App包住结构,以 render props 方式消费pageInfo |
| PWA / Manifest | 可选引入Manifest | 依据__pagePath输出application/manifest+json |
使用中的注意事项(均来自源码行为):
Root输出的 id 固定为root,运行时按此约定挂载,勿在页面中重复定义同名节点;Style、Script支持 props 透传,可利用此特性补充media、type等属性,但Script默认的crossorigin="anonymous"需要显式传值才能覆盖;- 自闭合
<script />在 HTML 中无效,Script组件已通过成对标签规避此问题,手写模板时也需留意; Data输出的全局变量名固定为window.__INITIAL_DATA__,与 rax-app 运行时的读取逻辑一一对应,改名会导致 SSR 数据在客户端丢失;App要求config中提供routes,且route.path需要与运行时的__pagePath精确匹配,路由配置参考 app.json。
七、总结
rax-document用六个声明式组件把 Rax 应用的 Document 层标准化:Root管理挂载点与 SSR 初始 HTML,Data注入服务端初始数据,Style与Script自动输出构建分析出的资源标签,App为多页应用提供基于路由的pageInfo,Manifest补充 PWA 能力。它们的共同特点是零配置接入——资源列表与页面数据均由构建器和渲染上下文自动注入,开发者只需在src/document/index.jsx中声明结构即可。结合仓库中的 核心实现 与各 示例项目,即可在任意 Rax 工程中快速搭建出支持 CSR、SSR 与 MPA 三种形态的页面骨架。
- 前端
【免费下载链接】rax
🐰 Rax is a progressive framework for building universal application. https://rax.js.org
相关推荐
探索桌面AI伴侣的完整指南:打造个性化虚拟伙伴的终极方案
探索桌面AI伴侣的完整指南:打造个性化虚拟伙伴的终极方案 呆啵宠物DyberPet是一款基于PySide6开发的桌面宠物框架,它将你喜欢的角色带到桌面上,提供动
桌面应用AI 应用交互助手Interview-for-Algorithm-Engineer中的目标跟踪技术:从传统方法到深度学习的面试准备指南
Interview for Algorithm Engineer中的目标跟踪技术:从传统方法到深度学习的面试准备指南 在算法工程师面试中,目标跟踪技术是计算机视
Rax与GraphQL集成:构建高效数据交互的应用
Rax与GraphQL集成:构建高效数据交互的应用 你是否还在为前端应用的数据交互烦恼?传统REST API带来的过度请求、数据冗余和前后端协作难题,正在消耗开
前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考