
Yew 0.20 版本深度解析服务器端渲染与 Suspense 数据获取正式落地【免费下载链接】yewRust / Wasm framework for creating reliable and efficient web applications项目地址: https://gitcode.com/gh_mirrors/ye/yew导读本文围绕 Yew 官方发布的 v0.20 版本博客展开聚焦本次更新的两大核心主题——服务器端渲染SSR与 render-as-you-fetch 数据获取方案。读完本文你将掌握ServerRenderer与Renderer::hydrate的完整使用方式、use_prepared_state!与 Suspense 的组合数据获取模式以及从 0.19 升级到 0.20 的全部破坏性变更清单并能在当前仓库的示例中直接验证这些能力。一、版本背景一次酝酿已久的重大更新Yew 0.20 是 Yew 团队宣布的一个long overdue久等了的版本。Yew 是一个用 Rust 编写、编译为 WebAssembly 的前端框架其官方定位是creating reliable and efficient web applications构建可靠、高效的 Web 应用。本次发布的核心目标围绕提升用户体验展开官方博客明确指出两大新能力服务器端渲染SSRYew 现在可以完全在服务器上渲染页面render-as-you-fetch 数据获取通过新加入的use_prepared_state!hook 与 Suspense 机制让数据获取方式更现代化。在传统客户端渲染CSR模式下用户访问网站时服务器只会发送一个不含实际内容的骨架 HTML 和一份 WebAssembly 包页面全部内容需要浏览器下载完整个 Wasm 包并完成首次渲染后才能看到。这带来两个痛点慢网络下用户体验极差且部分搜索引擎不支持动态渲染内容。SSR 正是为解决这些问题而引入的。二、服务器端渲染SSR从客户端渲染到服务端渲染2.1 核心 APIServerRendererYew 在yewcrate 中提供了ServerRenderer类型用于在服务器端渲染页面。官方文档 server-side-rendering 给出了最简用法创建ServerRenderer::App::new()后调用renderer.render().await即可将App /渲染为一个Stringuse yew::prelude::*; use yew::ServerRenderer; #[component] fn App() - Html { html! {div{Hello, World!}/div} } // we use flavor current_thread so this snippet can be tested in CI, // where tests are run in a WASM environment. You likely want to use // the (default) multi_thread favor as: // #[tokio::main] #[tokio::main(flavor current_thread)] async fn no_main() { let renderer ServerRenderer::App::new(); let rendered renderer.render().await; // Prints: divHello, World!/div println!({}, rendered); }这段代码展示了 SSR 的最基本流程render().await返回的字符串就是可以直接发送给浏览器的 HTML。SSR 的价值在于用户无需等待整个 WebAssembly 包下载并完成首次渲染就能看到内容页面立即可见且一旦水合hydration完成即可交互。2.2 SSR 环境下的组件生命周期约束从源码与文档可以确认SSR 对组件编写方式有明确约束推荐使用函数组件。官方文档明确建议The recommended way of working with server-side rendering is function components。除use_effect及use_effect_with外所有 hook 都能正常工作直到组件首次成功渲染为Html。Web API 在服务器端不可用web_sys等 Web API 在服务器端渲染时不可访问一旦使用会导致 panic。应把依赖 Web API 的逻辑隔离到use_effect/use_effect_with中因为 effect 在服务器端渲染期间不会执行。Struct 组件结构组件需谨慎虽然可以用 Struct 组件做 SSR但它没有像use_effect那样的客户端安全逻辑边界生命周期事件调用顺序与客户端不同且会持续接收消息直到所有子组件渲染完成并调用destroy。因此在设计 SSR 应用时应优先使用函数组件。2.3 水合Hydration让静态 HTML 恢复交互水合是把 Yew 应用连接到服务器生成的 HTML 文件的过程。默认情况下ServerRenderer输出的就是可水合的 HTML 字符串包含用于水合的附加信息。当调用Renderer::hydrate时Yew 不会从头开始渲染而是将应用生成的虚拟 DOM 与服务器渲染器生成的 HTML 字符串进行调和reconcile。use yew::prelude::*; use yew::Renderer; #[component] fn App() - Html { html! {div{Hello, World!}/div} } fn main() { let renderer Renderer::App::new(); // hydrates everything under body element, removes trailing // elements (if any). renderer.hydrate(); }水合有两个需要特别留意的约束虚拟 DOM 布局必须严格一致客户端产生的虚拟 DOM 布局必须与 SSR 时完全一致包括那些不含任何元素的组件。如果某个组件只在一种实现中有用可用PhantomComponent填补其位置。HTML 必须是规范兼容的水合只有在真实 DOM 与 SSR 输出静态 HTML经浏览器解析后一致时才能成功。如果 HTML 不符合规范浏览器可能改动 DOM 结构例如table缺少tbody时浏览器会补插tbody导致真实 DOM 与预期 DOM 不一致而使水合失败。水合期间组件创建后会调度两次连续渲染任何 effect 都在第二次渲染完成后调用。因此渲染函数必须无副作用——不应修改任何状态或触发额外渲染若有此类逻辑应移入use_effect。2.4 单线程模式LocalServerRenderer对于 WASI 等单线程环境Yew 提供yew::LocalServerRenderer支持单线程 SSR。官方文档给出示例使用wasm32-wasip1或wasm32-wasip2目标构建use yew::prelude::*; use yew::LocalServerRenderer; #[component] fn App() - Html { html! { h1{Yew WASI SSR demo}/h1 } } pub async fn render() - String { let renderer LocalServerRenderer::App::new(); let html_raw renderer.render().await; let mut body String::new(); body.push_str(body); body.push_str(div idapp); body.push_str(html_raw); body.push_str(/div); body.push_str(/body); body } #[tokio::main(flavor current_thread)] async fn main() { println!({}, render().await); }对应的完整示例位于仓库的 wasi_ssr_module。此外若使用wasm32-unknown-unknown目标构建 SSR 应用可以启用not_browser_envfeature 来禁止访问 Yew 内部的浏览器专属 API这对 Cloudflare Worker 之类的 serverless 平台非常有用。2.5 仓库示例验证simple_ssr与 e2e 测试当前仓库中的 simple_ssr 示例完整演示了 SSR 数据获取 水合的链路#[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 } }其对应的浏览器端 e2e 测试 e2e.rs 验证了水合成功先通过setup_ssr_page建立页面再用yew::Renderer::App::with_root(output_element()).hydrate()水合最后断言页面包含Random UUID:文本。这是 SSR 到水合全流程可工作的直接证据。仓库还提供了两个完整的服务器集成示例展示了如何将 SSR 接入真实后端框架axum_ssr_router基于 axum 的路由 SSR 演示actix_ssr_router基于 actix-web 的等价演示。这两个示例的核心是ServerApp组件它使用MemoryHistory构造内存路由历史接收请求 URL 与查询参数通过RouterLinkProvider渲染出与客户端一致的应用结构。三、数据获取use_prepared_state!与 SuspenseSSR 引入了新的数据获取方式。在 0.20 版本之前组件渲染时数据获取是老大难问题Yew 无法检测组件是否仍在获取数据数据获取客户端必须自行实现检测首次渲染期间请求了什么、请求完成后触发第二次渲染的方案服务器则要反复渲染直到没有新增挂起请求。这不仅浪费 CPU还要求数据客户端提供机制让服务器端获取的数据在水合期间可用实现难度极高。Yew 用Suspense /从根上解决了这个问题。Suspense 是一个特殊组件客户端使用时在组件获取数据挂起期间显示 fallback UI数据获取完成后恢复为正常 UI。3.1 服务端数据获取use_prepared_state!博客提到的use_prepared_state!hook 用于在服务器端渲染时获取数据并在组件中无缝使用。查看其源码 use_prepared_state/mod.rs 可以确认其完整签名与语义签名use_prepared_stateT, D, F(deps: D, f: F) - SuspensionResultOptionRcT约束D依赖与T返回类型都必须实现Serialize DeserializeOwnedD还需PartialEq接受同步闭包或异步闭包async |deps| - ReturnType { ... }在服务器端渲染期间会用第一个闭包计算出类型为T的值如果包在编译时没有 SSR 特性闭包会被裁剪掉stripped对应源码位于packages/yew/src/functional/hooks/use_prepared_state/下的feat_ssr.rs、feat_hydration.rs、feat_hydration_ssr.rs、feat_none.rs四个按 feature 区分的实现文件中。其核心语义是使用一个在服务器端准备的状态其值在水合期间发送给客户端——被水合的组件在服务器端和客户端看到相同的值。配合 Suspense 的完整用法即上文 simple_ssr 示例use_prepared_state!返回SuspensionResult用?解包后获得OptionRcT再unwrap()取得数据外层再用Suspense fallback{...}包裹等待服务器端数据获取完成。3.2 客户端数据获取render-as-you-fetch 与 Suspense对于客户端数据获取Yew 支持 render-as-you-fetch 方法详见 suspense 概念文档。其核心机制是组件在渲染过程中发起数据请求渲染过程随即挂起fallback UI 显示直至请求完成。推荐的使用方式是与 hook 结合。一个挂起式suspendinghook 需要返回SuspensionResultT当需要挂起组件时返回Err(Suspension)并通过?解包会转换为Htmluse yew::prelude::*; use yew::suspense::{Suspension, SuspensionResult}; struct User { name: String, } #[hook] fn use_user() - SuspensionResultUser { match load_user() { // If a user is loaded, then we return it as Ok(user). Some(m) Ok(m), None { // When user is still loading, then we create a Suspension // and call SuspensionHandle::resume when data loading // completes, the component will be re-rendered // automatically. let (s, handle) Suspension::new(); on_load_user_complete(move || {handle.resume();}); Err(s) }, } } #[component(Content)] fn content() - HtmlResult { let user use_user()?; Ok(html! {div{Hello, }{user.name}/div}) } #[component(App)] fn app() - Html { let fallback html! {div{Loading...}/div}; html! { Suspense {fallback} Content / /Suspense } }这里Suspension::new()返回两个值挂起上下文本身和一个挂起句柄suspension handle。句柄负责通知何时重新渲染挂起的组件有两种等价方式调用其resume方法或直接丢弃drop句柄。⚠️ 注意挂起句柄必须被存储到更新组件例如拿到新数据的时刻否则挂起组件会陷入无限重渲染循环严重影响性能。上述示例中句柄被移入闭包并传给on_load_user_complete当用户数据加载完成时闭包被调用进而调用handle.resume()触发关联组件重渲染。Struct 组件无法直接挂起但可以借助高阶组件Higher Order Component模式——用一个函数组件包裹 Struct 组件来实现基于 Suspense 的数据获取。仓库中的 suspense 示例 同时演示了函数组件消费者与 struct_consumer.rs 两种用法并包含一个自定义的use_sleep挂起 hookuse_sleep.rs展示了点击按钮后挂起 5 秒再恢复的完整交互。3.3 Suspense 在 SSR 中的角色将两者结合Yew 的 SSR 数据获取方案就非常清晰了服务器端渲染时Yew 会一直等待组件不再挂起才将其序列化进字符串缓冲区水合过程中Suspense /内部的元素保持脱水dehydrated状态直到其所有子组件都不再挂起。由此开发者可以用极小的成本构建一个客户端无关、SSR 就绪的、带数据获取的应用——这正是 0.20 版本提升用户体验目标的落地点。四、从 0.19 升级到 0.20破坏性变更全清单官方迁移指南 from-0_19_0-to-0_20_0.mdx 列出了本次发布的全部破坏性变更升级时需逐一对照4.1start_app的_as_body变体被移除start_app中用于控制 body 的_as_body变体因在事件注册和 SSR 水合中引发问题而被移除。4.2 新的 Hooks 与函数组件 API函数组件与 Hooks API 用全新机制重新实现用户自定义 hook必须以use_前缀命名必须用#[hook]属性标记如果 hook 不是在函数组件或用户自定义 hook 的顶层调用现在会在编译期报错旧版本同样存在此限制但 0.20 起作为编译错误报告。4.3 自动消息批处理Automatic Message Batching调度器现在会把调度起点安排到浏览器事件循环的末尾期间排队的所有消息会批量执行不同组件之间消息的执行顺序不再保证但发往同一组件的消息仍按 FIFO 顺序确认若多次更新会导致渲染组件只会渲染一次对开发者意味着给两个不同组件各发两条消息时不能假设它们按发送顺序被处理给同一组件发消息时仍按顺序。消息不会立即发送给组件因此不要假设组件收到消息时仍保留创建消息时的状态。对函数组件如果用use_state(_eq)存储状态且新值依赖旧值建议改用use_reducer(_eq)——状态的新值要到组件下一次渲染才可见/被确认而 reducer action 与 Struct 组件的消息类似会按 dispatch 顺序送入 reducer 函数reducer 执行时能看到此前所有的变更。4.4yew::Renderer取代start_app*start_app*系列方法被yew::Renderer取代使用yew::Renderer需要启用csrfeaturelet renderer yew::Renderer::App::new(); renderer.render(); // 客户端渲染 // 或 renderer.hydrate(); // 水合已有 SSR 输出4.5 组件的refprop 被移除组件不再拥有refprop尝试给组件添加节点引用会直接编译报错。旧行为节点引用绑定到组件渲染的第一个元素如仍需要建议在组件 properties 中自行添加r#ref字段并手动绑定。4.6changed方法签名变化fn changed()新增一个参数用于提供旧 properties// 旧签名 fn changed(mut self, ctx: ContextSelf) - bool // 新签名 fn changed(mut self, ctx: ContextSelf, old_props: Self::Properties) - bool迁移指南还提供了可自动调整的 bash 脚本使用前请先保存代码perl -p -i -e s/fn changed\(mut self, (\w): ContextSelf\)/fn changed(mut self, $1: ContextSelf, _old_props: Self::Properties)/g $(find . -name \*.rs)五、总结0.20 为 Yew 带来的范式转变Yew 0.20 是一次意义重大的发布SSR 从可以渲染走向完整支持ServerRenderer、LocalServerRenderer、Renderer::hydrate构成了完整的服务端渲染与客户端水合工具链use_prepared_state!与 Suspense 则把数据获取从反复重渲染的笨拙模式统一到渲染时挂起、就绪后恢复的现代化范式。与此同时自动消息批处理、编译期 hook 校验和RendererAPI 统一等基础能力的重构为后续版本0.21、0.22、0.23 乃至当前主分支的 SSR 生态演进例如yew-link统一数据获取、Axum/Actix 集成示例奠定了坚实基础。若要在本地验证本文所述能力可直接运行仓库中的示例examples/simple_ssrSSR 水合最小闭环、examples/suspense函数/Struct 组件双消费者、examples/axum_ssr_router与examples/actix_ssr_router后端框架集成。具体运行方式请参考各示例目录下的 README 与 Cargo.toml 中的 feature 配置。【免费下载链接】yewRust / Wasm framework for creating reliable and efficient web applications项目地址: https://gitcode.com/gh_mirrors/ye/yew创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考