Quasar CLI with Vite - @quasar/app-vite

客户端水化

提示(译者批注,非官方文档内容)

理解本页面的前提是您得知道:由 SSR 服务端返回的 html 是静态的,不可以进行交互,所以才需要下文中的水化步骤使静态的页面变成动态 DOM。

Hydration (水化/水合作用)是指客户端中,Vue 接管由服务器发送的静态 HTML,并将其转换为动态 DOM 的过程,使其可以对客户端数据变化做出反应。

由于服务器已经渲染了静态的 html 标签,我们显然不想将其丢弃并重新创建所有 DOM 元素。取而代之的是,我们想"hydrate"(补充水分/水化/水合)静态标签并使其具有交互性。

警告

在开发模式下,Vue 将断言客户端生成的虚拟 DOM 树与从服务器呈现的 DOM 结构匹配。如果存在不匹配,它将放弃现有 DOM 并从头开始渲染。在生产模式下,为了获得最佳性能,将禁用此断言。

激活不匹配

在使用 SSR + 客户端水化时,有一件事情需要注意:使用一些特殊的 HTML 时要符合浏览器的 HTML 规范,如果组件模板中存在不符合规范的 HTML 结构,那么渲染后的 HTML 会被浏览器原生的 HTML 解析行为纠正,导致不匹配。例如,当您在 Vue 的模板中写入以下代码时:

<table>
  <tr><td>hi</td></tr>
</table>

浏览器的解析行为会自动在 <table> 中注入 <tbody> 标签。然而 Vue 的虚拟 DOM 生成的 HTML 却不包含 <tbody> 标签,所以会造成不匹配,为确保激活匹配,请书写标准的 HTML 模板。

提示(夹带私货非官方文档内容)

激活不匹配还需要注意另外两件事情:

  1. 渲染所用的数据中包含随机生成的值。由于同一个应用会在服务端和客户端执行两次,每次执行生成的随机数都不能保证相同。避免随机数不匹配有两种选择:
  • 利用 v-if + onMounted 让需要用到随机数的模板只在客户端渲染。您所用的上层框架可能也会提供简化这个用例的内置 API,比如 VitePress 的 <ClientOnly> 组件。
  • 使用一个能够接受随机种子的随机数生成库,并确保服务端和客户端使用同样的随机数种子 (比如把种子包含在序列化的状态中,然后在客户端取回)。
  1. 服务端和客户端的时区不一致。有时候我们可能会想要把一个时间转换为用户的当地时间,但在服务端的时区跟用户的时区可能并不一致,我们也并不能可靠的在服务端预先知道用户的时区。这种情况下,当地时间的转换也应该作为纯客户端逻辑去执行。

当 Vue 遇到激活不匹配时,它将尝试自动恢复并调整预渲染的 DOM 以匹配客户端的状态。这将导致一些渲染性能的损失,因为需要丢弃不匹配的节点并渲染新的节点,但大多数情况下,应用应该会如预期一样继续工作。尽管如此,最好还是在开发过程中发现并避免激活不匹配。

更多关于激活不匹配信息参考 vue 文档

处理水化错误

如果确实收到了一个水化失败的报错(列如:“Vuejs Error - The client-side rendered virtual DOM tree is not matching server-rendered content”) 可以尝试以下步骤:

  1. 打开浏览器的调试面板 (F12)
  2. 加载导致报错的页面(“the client-side rendered virtual DOM tree…”)
  3. 在调试面板的控制台中找到报错。
  4. 点击报错中的源代码的路径链接,进入 vue.runtime.esm.js.
  5. 在报错处设置一个断点(点击行号的左边)。
  6. 复现错误,通常刷新页面就可以复现,报错时将鼠标移到msg变量上可以得到详细的信息。
  7. 当您得到信息,并停在断点处,查看_call stack_(函数调用栈)单击下一帧调用“patch”以打开其源代码,鼠标移到 hydrate 函数上,打开 hydrate 函数的源码。
  8. 在 hydrate 函数的源码中,从开始往下大概 15 行的地方,可以看到assertNodeMatch函数返回false,在此处设置一个断点,并移除其他的断点。
  9. 重新复现错误,然后发现断点生效,代码执行停在 hydrate 函数中。切换到调试面板的控制台,依次输入elmvnode命令。这里elm是服务端渲染的 DOM,vnode是 Vue 生成的虚拟 DOM,您可以对比它们的不同然后排查错误。