跳至主要内容
版本:0.21

服务器端渲染

默认情况下,Yew 组件在客户端渲染。当访问者访问网站时,服务器会向浏览器发送一个不包含任何实际内容的 HTML 骨架文件和一个 WebAssembly 捆绑包。所有内容都由 WebAssembly 捆绑包在客户端渲染。这称为客户端渲染。

这种方法适用于大多数网站,但有一些需要注意的地方

  1. 在整个 WebAssembly 捆绑包下载并完成初始渲染之前,用户将无法看到任何内容。对于网络速度较慢的用户,这可能会导致糟糕的体验。
  2. 一些搜索引擎不支持动态渲染的网络内容,而那些支持的搜索引擎通常会将动态网站在搜索结果中排名较低。

为了解决这些问题,我们可以在服务器端渲染我们的网站。

工作原理

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 API 不可使用!

当你的组件在服务器端渲染时,诸如 web_sys 的 Web API 不可使用。如果你尝试使用它们,你的应用程序将崩溃。你应该将需要 Web API 的逻辑隔离在 use_effectuse_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 上提交问题。