# 编写通用的代码
在更进一步之前,让我们花一些时间讨论一下编写“通用的”代码时的约束——也就是,同时运行在服务端和客户端的代码。因为用例及平台 API 的不同,运行于不同环境下的代码行为也会不尽相同。我们在此会阐述一些需要注意的关键环节。
# 服务端的数据响应性
在仅针对客户端的应用中,每个用户都在各自的浏览器中使用一个干净的应用实例。对于服务端渲染来说我们也希望如此:每个请求应该拥有一个干净的、相互隔离的应用实例,以避免跨请求的状态污染。
因为实际的渲染过程需要确定性,我们也会从服务器“预获取”数据——这意味着应用状态在我们开始渲染之前已经被解析好了。这也意味着数据响应性在服务端不是必要的,因此它默认是不开启的。禁用数据响应性也避免了将数据转换为响应式对象的性能损耗。
# 组件生命周期钩子
因为这里没有动态更新,唯一会在 SSR 过程中被调用的生命周期钩子是 beforeCreate
和 created
。这意味着其它生命周期钩子 (如 beforeMount
或 mounted
) 中的任何代码将只会在客户端执行。
另一个值得注意的是你应该避免代码在 beforeCreate
或 created
中产生全局的副作用,例如通过 setInterval
设置定时器。在仅针对客户端的代码中,我们可以设置定时器,然后在 beforeUnmount
或 unmounted
时撤掉。然而,因为销毁相关的钩子在 SSR 过程中不会被调用,这些定时器就会永久地保留下来。为了避免这种情况,请把副作用代码移至 beforeMount
或 mounted
以代之。
# 访问特定平台的 API
通用的代码不能假设能够访问特定平台的 API,因此,如果你的代码直接使用了只存在于浏览器中的全局变量,例如 window
或 document
,它们在 Node.js 中执行的时候将抛出错误。反之亦然。
对共享于服务端和客户端之间但使用不同平台 API 的任务来说,我们推荐把这些特定平台的实现包裹在一个通用的 API 里,或使用现有的库来替你做这件事。例如 axios (opens new window) 是一个在服务端和客户端暴露相同 API 的 HTTP 客户端。
对于只存在于浏览器中的 API 来说,通常的建议是晚些时候在针对客户端的生命周期钩子中访问。
注意:如果一个第三方库在编写的时候没有考虑通用的用法,那么要将它集成到服务端渲染应用中可能会比较棘手。你可以通过仿造一些全局变量让其工作起来,但是这种做法带有侵入性,且可能会妨碍到其它库的环境监测代码。
# 自定义指令
大多数自定义指令都会直接操作 DOM,这会导致 SSR 过程发生错误。所以我们推荐使用组件这种抽象机制替代指令。