Yew 服务端渲染SSR完整指南ServerRenderer、Suspense 数据获取与 Hydration 实战【免费下载链接】yewRust / Wasm framework for creating reliable and efficient web applications项目地址: https://gitcode.com/gh_mirrors/ye/yew导读本文基于 Yew 官方 0.21 版本文档中的服务端渲染Server-side Rendering, SSR专题系统讲解 Yew 应用如何在服务端预渲染出 HTML从ServerRenderer的基础用法、函数组件与 Struct 组件在 SSR 下的生命周期差异到利用Suspense /优雅解决服务端数据获取再到通过Renderer::hydrate将客户端 Virtual DOM 与静态 HTML 拼接为可交互页面。读完本文你将掌握一套完整、可落地的 SSR Hydration 架构并能在仓库源码与示例中找到每一处机制的实现依据。为什么需要服务端渲染客户端渲染的两大痛点默认情况下Yew 组件在客户端渲染CSR。当用户访问网站时服务器只发送一个不含实际内容的 HTML 骨架文件和一个 WebAssembly 包到浏览器页面完全由浏览器端加载的 WASM 包负责渲染。这种方案对大多数网站都能正常工作但存在两个明显问题首屏等待在 WebAssembly 包完整下载并完成初次渲染之前用户什么都看不到。对于慢速网络下的用户这是非常糟糕的体验。SEO 不利部分搜索引擎不支持动态渲染的网页内容即使支持也通常会给予动态网站较低的搜索排名。服务端渲染正是为了解决这两类问题让服务器直接输出包含内容的 HTML用户与搜索引擎爬虫在收到响应时即可看到完整页面。如何工作ServerRenderer基础用法Yew 提供了ServerRenderer组件结构体用于在服务端渲染页面。最简用法是用ServerRenderer::App::new()创建渲染器然后await调用renderer.render()将App /渲染成一个Stringuse yew::prelude::*; use yew::ServerRenderer; #[function_component] fn App() - Html { html! {div{Hello, World!}/div} } // 我们使用 flavor current_thread 以便该代码片段可以在 CI 中测试 // CI 里测试运行在 WASM 环境中。实际项目中你更可能希望使用默认的 // multi_thread 形式即 // #[tokio::main] #[tokio::main(flavor current_thread)] async fn no_main() { let renderer ServerRenderer::App::new(); let rendered renderer.render().await; // 输出: divHello, World!/div println!({}, rendered); }从当前仓库源码 server_renderer.rs 可以看到ServerRenderer会将渲染任务派发到 Yew 的Runtime上执行spawn_rendering_task通过 oneshot 通道取回结果而LocalServerRenderer同一文件 server_renderer.rs则直接在当前线程内渲染不自行启动运行时。ServerRenderer还提供了三个值得了解的配套能力render_to_string(w: mut String)把渲染结果追加到已存在的String中便于拼接页面骨架。render_stream()以StreamItem String形式流式输出 HTML 片段适合在 Web 框架中逐块写回响应。with_props(create_props)为根组件传入自定义Properties属性闭包只需Send属性本身无需Send另有with_runtime(rt)可指定渲染任务运行的Runtime。hydratable(val: bool)设置输出是否包含用于水合hydration的附加信息默认值为true。注意ServerRenderer与LocalServerRenderer均在ssr特性feature下编译见 Cargo.toml使用时需在依赖中开启yew { path ../../packages/yew, features [ssr] }组件生命周期与 SSR函数组件为主慎用 Struct 组件推荐使用函数组件在服务端渲染场景下官方推荐使用函数组件。除use_effect以及use_effect_with之外的所有 Hooks 都会在组件首次成功渲染为Html之前正常工作。⚠️ 警告服务端没有 Web APIs当组件在服务端渲染时web_sys等 Web API不可用。一旦尝试使用应用会直接 panic。由于 effects 在服务端渲染期间不会执行你应该把依赖 Web API 的逻辑隔离到use_effect或use_effect_with中——这正是二者在服务端被跳过的意义所在也是区分纯渲染逻辑与浏览器副作用的天然边界。危险Struct 组件虽然 Struct 组件理论上也能用于 SSR但存在两个隐患缺少函数组件use_effect那样明确的客户端安全逻辑边界且生命周期事件如view、rendered的调用顺序与客户端不同Struct 组件会持续接收消息直到其所有子组件都渲染完成且destroy方法被调用。开发者必须保证任何可能传给组件的消息都不会触达依赖 Web API 的逻辑。因此在设计支持 SSR 的应用时除非有充分理由否则一律优先使用函数组件。SSR 中的数据获取用Suspense /取代反复渲染数据获取是 SSR 与 Hydration 中最棘手的环节之一。组件渲染时本应立刻产出 Virtual DOM这在组件不需要取数时没有问题。但组件若想渲染期间获取数据呢过去 Yew 没有机制探测组件是否仍在取数只能由数据客户端自行实现方案记录初次渲染期间发起了哪些请求等请求完成后再触发第二次渲染服务器反复执行这一过程直到某次渲染不再新增挂起请求才返回响应。这种方案有两个缺陷一是反复渲染浪费 CPU二是数据客户端还必须想办法让服务端取到的数据在 Hydration 阶段可用以保证初次渲染产出的 Virtual DOM 与 SSR 产出的 DOM 树一致实现难度很高。Yew 采用了不同的思路——用Suspense /解决客户端Suspense /是一个特殊组件在组件取数期间即挂起状态显示 fallback 占位 UI取数完成后恢复为正常 UI服务端Yew 会一直等待直到组件不再挂起才将其序列化进输出字符串Hydration 阶段Suspense /内的元素会保持未水合dehydrated状态直到其所有子组件都不再挂起。开发者因此几乎不需要额外工作就能构建出客户端无关、开箱即用 SSR 的数据获取应用。底层机制Suspension挂起机制的核心类型是Suspension见 suspension.rs。它内部维护id、监听器列表与resumed原子标志Suspension::from_future会把一个 Future 派发到本地任务spawn_localFuture 完成后调用SuspensionHandle::resume()唤醒所有监听者Suspension本身实现了Future未恢复时返回Poll::Pending。相关的use_future/use_future_withHook见 hooks.rs返回SuspensionResultUseFutureHandleO未就绪时以Err(Suspension)形式让组件挂起。Suspense /组件的 Props 只有两个children内容与fallback占位 UI见 component.rs。注意约束你不能在一个作为 fallback 渲染的组件里再次挂起见 component.rs。实战示例use_prepared_state!取数仓库中的 simple_ssr 示例 完整演示了 Suspense use_prepared_state 的数据获取模式Content组件在服务端用use_prepared_state!发起一个真实的 HTTP 请求请求https://httpbingo.org/uuid获得随机 UUID外层用Suspense包裹并提供 fallbackuse serde::{Deserialize, Serialize}; use uuid::Uuid; use yew::prelude::*; #[derive(Serialize, Deserialize)] struct UuidResponse { uuid: Uuid, } #[cfg(feature ssr)] async fn fetch_uuid() - Uuid { let client reqwest::Client::builder() .user_agent(yew-simple-ssr-example) .build() .unwrap(); let resp client .get(https://httpbingo.org/uuid) .send() .await .unwrap() .error_for_status() .unwrap(); let uuid_resp resp.json::UuidResponse().await.unwrap(); uuid_resp.uuid } #[function_component] fn Content() - HtmlResult { let uuid use_prepared_state!((), async move |_| - Uuid { fetch_uuid().await })?.unwrap(); Ok(html! { div{Random UUID: }{uuid}/div }) } #[function_component] pub fn App() - Html { let fallback html! {div{Loading...}/div}; html! { Suspense {fallback} Content / /Suspense } }use_prepared_state!是过程宏实现见 use_prepared_state.rs宏要求闭包必须显式标注返回类型该类型在客户端剥离闭包时用于生成占位代码服务端构建时生成use_prepared_state_with_suspension调用并执行闭包客户端构建时则通过to_token_stream_without_closure彻底剥离取数闭包只读取服务端序列化好的数据。这样服务端取到的 UUID 会被嵌入 HTML 并随水合过程送达客户端客户端首屏渲染不发起任何额外请求。SSR Hydration把静态 HTML 接上 Virtual DOMHydration 是把 Yew 应用连接到服务端生成的 HTML 文件的过程。默认情况下ServerRenderer输出的是可水合的HTML 字符串——其中包含便于 Hydration 的附加信息。当客户端调用Renderer::hydrate时见 renderer.rsYew 不再从零渲染而是将应用产出的 Virtual DOM 与服务端渲染器生成的 HTML 字符串进行对账reconcile。客户端示例use yew::prelude::*; use yew::Renderer; #[function_component] fn App() - Html { html! {div{Hello, World!}/div} } fn main() { let renderer Renderer::App::new(); // 水合 body 元素下的所有内容移除多余的尾部元素如果有。 renderer.hydrate(); }⚠️ 注意事项一Virtual DOM 布局必须完全一致要成功水合由ServerRenderer创建的 HTML 表示客户端产出的 Virtual DOM 布局必须与 SSR 时完全一致包括那些不包含任何元素的组件。如果某个组件只在其中一端有意义可以用PhantomComponent来填补多出的组件位置。⚠️ 注意事项二HTML 必须规范Hydration 只有在浏览器对 SSR 静态 HTML 做初次渲染后的真实 DOM 与预期 DOM 一致时才能成功。如果 HTML 不符合规范Hydration 可能失败——浏览器会自行改写不正确的 HTML 结构导致真实 DOM 与预期 DOM 不一致。典型例子是table缺少tbody时浏览器会自动补上一个tbody使 DOM 结构发生变化。Hydration 期间的组件生命周期Hydration 过程中组件创建后会连续调度两次渲染任何 effects 都要等第二次渲染完成后才被调用。因此务必保证组件渲染函数无副作用不应变更任何状态或触发额外渲染如果组件当前有这类行为请把它们移入use_effectHook。若在 Hydration 中使用 Struct 组件view函数会在rendered函数被调用之前被多次调用在rendered()被调用之前DOM 被视为未连接必须避免在此期间访问已渲染的节点。端到端工程示例simple_ssr 的项目结构与运行方式仓库中的 simple_ssr 是一个完整可运行的 SSR Hydration 示例。其 Cargo.toml 定义了与 SSR 相关的特性开关与二进制目标[[bin]] name simple_ssr_hydrate required-features [hydration] [[bin]] name simple_ssr_server required-features [ssr] [features] hydration [yew/hydration] ssr [yew/ssr]按 README 的指引运行分为两步# 1. 构建 Hydration 包 trunk build index.html # 2. 运行服务器warp 后端为静态 HTML 与 SSR 页面提供服务 cargo run --featuresssr --bin simple_ssr_server -- --dir dist该示例还包含一个浏览器端 e2e 测试见 tests/e2e.rs测试启动 SSR 页面后调用yew::Renderer::App::with_root(output_element()).hydrate()执行水合并断言页面内容包含Random UUID:——这直接验证了 服务端预渲染 客户端水合 全链路的数据一致性。特性开关与当前版本演进SSR 相关能力由 Yew 的 Cargo features 控制见 packages/yew/Cargo.tomlfeature说明ssr启用服务端渲染同时引入html-escape、base64ct、bincode依赖用于 HTML 转义与数据序列化csr启用客户端渲染Renderer等入口hydration启用水合依赖csr与bincodenot_browser_env禁用 Yew 内部的浏览器特定 API 访问适用于 Cloudflare Workers 等无浏览器环境serde为implicit-clone启用 serde 支持在 0.21 版本文档发布时SSR 仍被标记为experimental实验性官方建议发现 bug 时提交 issue。随着仓库演进到当前主线0.23SSR 能力已进一步扩展新增了yew::LocalServerRenderer以支持 WASI 等单线程环境可参考 wasi_ssr_module 示例、not_browser_env特性以及 Axum / Actix 等 Web 框架的集成示例axum_ssr_router、actix_ssr_router这些均可作为深入学习 SSR 应用架构的参考详见当前版本文档 server-side-rendering.mdx。小结Yew 的 SSR 方案围绕三个支柱展开ServerRenderer负责服务端产 HTML、Suspense /负责统一服务端与客户端的异步数据获取、Renderer::hydrate负责把静态 HTML 接续为可交互的 Yew 应用。实践中的关键纪律是优先函数组件、把 Web API 相关逻辑隔离进 effects、保证渲染函数纯净且两端 Virtual DOM 布局一致。把握住这几点即可构建出兼顾首屏速度、SEO 与交互体验的 Yew 全栈应用。【免费下载链接】yewRust / Wasm framework for creating reliable and efficient web applications项目地址: https://gitcode.com/gh_mirrors/ye/yew创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
