服务器端渲染
默认情况下,Yew 组件在客户端渲染。当访问者访问网站时,服务器会向浏览器发送一个不包含任何实际内容的 HTML 骨架文件和一个 WebAssembly 捆绑包。所有内容都由 WebAssembly 捆绑包在客户端渲染。这称为客户端渲染。
这种方法适用于大多数网站,但有一些需要注意的地方
- 在整个 WebAssembly 捆绑包下载并完成初始渲染之前,用户将无法看到任何内容。对于网络速度较慢的用户,这可能会导致糟糕的体验。
- 一些搜索引擎不支持动态渲染的网络内容,而那些支持的搜索引擎通常会将动态网站在搜索结果中排名较低。
为了解决这些问题,我们可以在服务器端渲染我们的网站。
工作原理
Yew 提供了一个 ServerRenderer
来在服务器端渲染页面。
要在服务器端渲染 Yew 组件,可以使用 ServerRenderer::<App>::new()
创建一个渲染器,并调用 renderer.render().await
将 <App />
渲染到 String
中。
use yew::prelude::*;
use yew::ServerRenderer;
#[function_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: <div>Hello, World!</div>
println!("{}", rendered);
}
组件生命周期
使用服务器端渲染的推荐方式是函数组件。
除了 use_effect
(和 use_effect_with
)之外的所有钩子都将在组件首次成功渲染为 Html
之前正常运行。
当你的组件在服务器端渲染时,诸如 web_sys
的 Web API 不可使用。如果你尝试使用它们,你的应用程序将崩溃。你应该将需要 Web API 的逻辑隔离在 use_effect
或 use_effect_with
中,因为在服务器端渲染期间不会执行效果。
虽然可以在服务器端渲染中使用结构组件,但函数组件的客户端安全逻辑(如 use_effect
钩子)与生命周期事件之间没有明确的界限,并且生命周期事件的调用顺序与客户端不同。
此外,结构组件将继续接收消息,直到其所有子组件都被渲染并且调用了 destroy
方法。开发人员需要确保传递给组件的任何消息都不会链接到使用 Web API 的逻辑。
在设计支持服务器端渲染的应用程序时,除非你有充分的理由不这样做,否则优先使用函数组件。
服务器端渲染期间的数据获取
数据获取是服务器端渲染和水合中的一个难点。
传统上,当组件渲染时,它会立即可用(输出一个要渲染的虚拟 DOM)。当组件不想获取任何数据时,这种方法很好用。但如果组件在渲染期间想要获取一些数据,会发生什么情况?
过去,Yew 没有机制来检测组件是否仍在获取数据。数据获取客户端负责实现一个解决方案,以检测在初始渲染期间请求的内容,并在请求得到满足后触发第二次渲染。服务器重复此过程,直到在返回响应之前,渲染期间不再添加更多待处理请求。
这不仅会通过重复渲染组件而浪费 CPU 资源,而且数据客户端还需要提供一种方法,以便在水合过程中使用服务器端获取的数据,以确保初始渲染返回的虚拟 DOM 与服务器端渲染的 DOM 树一致,这可能很难实现。
Yew 采用不同的方法,尝试通过 <Suspense />
来解决此问题。
Suspense 是一种特殊组件,当在客户端使用时,它提供了一种方法,可以在组件获取数据(挂起)时显示备用 UI,并在数据获取完成后恢复到正常 UI。
当应用程序在服务器端渲染时,Yew 会等到组件不再挂起,才会将其序列化到字符串缓冲区中。
在水合过程中,<Suspense />
组件中的元素会保持脱水状态,直到其所有子组件不再挂起。
通过这种方法,开发人员可以构建一个与客户端无关的、支持 SSR 的应用程序,只需很少的精力即可获取数据。
SSR 水合
水合是将 Yew 应用程序连接到服务器端生成的 HTML 文件的过程。默认情况下,ServerRender
会打印可水合的 HTML 字符串,其中包含有助于水合的其他信息。当调用 Renderer::hydrate
方法时,Yew 不会从头开始渲染,而是会将应用程序生成的虚拟 DOM 与服务器渲染器生成的 HTML 字符串进行协调。
要成功水合由 ServerRenderer
创建的 HTML 表示,客户端必须生成一个虚拟 DOM 布局,该布局与用于 SSR 的布局完全匹配,包括不包含任何元素的组件。如果你有任何仅在一个实现中才有用的组件,则可能需要使用 PhantomComponent
来填充额外组件的位置。
如果浏览器在初始渲染 SSR 输出(静态 HTML)后,真实的 DOM 与预期的 DOM 相匹配,则水合才能成功。如果你的 HTML 不符合规范,则水合可能会失败。浏览器可能会更改不正确 HTML 的 DOM 结构,导致实际 DOM 与预期的 DOM 不同。例如,如果你有一个没有 <tbody>
的 <table>
,浏览器可能会向 DOM 中添加一个 <tbody>
水合期间的组件生命周期
在水合期间,组件在创建后会安排连续进行 2 次渲染。任何效果都会在第二次渲染完成后调用。确保组件的渲染函数没有副作用非常重要。它不应改变任何状态或触发额外的渲染。如果你的组件当前改变了状态或触发了额外的渲染,请将它们移到 use_effect
钩子中。
可以在水合中使用带有服务器端渲染的结构组件,在调用渲染函数之前,视图函数将被调用多次。在调用渲染函数之前,DOM 被认为没有连接,你应阻止对渲染节点的任何访问,直到调用 rendered()
方法。
示例
use yew::prelude::*;
use yew::Renderer;
#[function_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();
}
示例:simple_ssr示例:ssr_router
服务器端渲染目前处于实验阶段。如果你发现错误,请在 GitHub 上提交问题。