# 编写通用的代码

在更进一步之前,让我们花一些时间讨论一下编写“通用的”代码时的约束——那就是代码同时运行在服务端和客户端。因为用例及平台 API 不同,运行于不同环境下的代码行为也会不尽相同。我们在此会涉及一些需要注意的关键环节。

# 服务端的数据响应性

在只有客户端的应用中,每个用户都在各自的浏览器中使用一个干净的应用实例。对于服务端渲染来说我们也希望如此:每个请求应该拥有一个干净的相互隔离的应用实例,以避免跨请求的状态污染。

因为实际渲染过程需要确定性,我们也会从服务器“预获取”数据——这意味着应用状态在我们开始渲染之前已经被解析好了。这也意味着数据响应性在服务端是不必要的,因此它默认是不开启的。禁用数据响应性也避免了将数据转换为响应式对象的性能损耗。

# 组件生命周期钩子

因为这里没有动态更新,唯一会在 SSR 过程中被调用的生命周期钩子beforeCreatecreated。这意味着其它生命周期钩子诸如 beforeMountmounted 只会在客户端被执行。

另一个值得注意的是你应该避免代码在 beforeCreatecreated 中产生全局的副作用,例如通过 setInterval 设置定时器。在只有客户端的代码中我们可以设置定时器然后在 beforeUnmountunmounted 时撤掉。然而,因为销毁相关的钩子在 SSR 过程中不会被调用,这些定时器就会永久地保留下来。为了避免这件事,请把副作用移至 beforeMountmounted 以代之。

# 访问特定平台的 API

通用的代码无法假设对特定平台 API 的访问,因此如果你的代码直接使用只存在于浏览器的全局变量例如 windowdocument,它们会在 Node.js 里执行的时候抛出错误。反之亦然。

对共享于服务端和客户端但使用不同平台 API 的任务来说,我们推荐把这些特定平台的实现包裹在一个通用的 API 里,或使用现有的库来替你做这件事。例如 axios (opens new window) 是一个在服务端和客户端暴露相同 API 的 HTTP 客户端。

对于只存在于浏览器的 API 来说,通常的建议是晚些时候在只存在于客户端的生命周期钩子里访问。

注意如果一个三方库没有为通用的用法而编写,它可能需要技巧性地集成到服务端渲染应用中。你可以通过仿造一些全局变量让其工作起来,但是会比较“hacky”且可能妨碍到其它库的环境监测代码。

# 自定义指令

大多数自定义指令都会直接操作 DOM,这会导致 SSR 的错误。所以我们推荐使用组件这种抽象机制而不是指令。