编写通用代码
在进一步介绍之前,让我们花点时间来讨论编写"通用"代码时的约束条件 - 即运行在服务器和客户端的代码。由于用例和平台 API 的差异,当运行在不同环境中时,我们的代码将不会完全相同。所以这里我们将会阐述你需要理解的关键事项。
服务器上的数据响应
在纯客户端应用程序 (client-only app) 中,每个用户会在他们各自的浏览器中使用新的应用程序实例。对于服务器端渲染,我们也希望如此:每个请求应该都是全新的、独立的应用程序实例,以便不会有交叉请求造成的状态污染 (cross-request state pollution)。
因为实际的渲染过程需要确定性,所以我们也将在服务器上“预取”数据 ("pre-fetching" data) - 这意味着在我们开始渲染时,我们的应用程序就已经解析完成其状态。也就是说,将数据进行响应式的过程在服务器上是多余的,所以默认情况下禁用。禁用响应式数据,还可以避免将「数据」转换为「响应式对象」的性能开销。
组件生命周期钩子函数
由于没有动态更新,所有的生命周期钩子函数中,只有 beforeCreate
和 created
会在服务器端渲染 (SSR) 过程中被调用。这就是说任何其他生命周期钩子函数中的代码(例如 beforeMount
或 mounted
),只会在客户端执行。
此外还需要注意的是,你应该避免在 beforeCreate
和 created
生命周期时产生全局副作用的代码,例如在其中使用 setInterval
设置 timer。在纯客户端 (client-side only) 的代码中,我们可以设置一个 timer,然后在 beforeDestroy
或 destroyed
生命周期时将其销毁。但是,由于在 SSR 期间并不会调用销毁钩子函数,所以 timer 将永远保留下来。为了避免这种情况,请将副作用代码移动到 beforeMount
或 mounted
生命周期中。
访问特定平台(Platform-Specific) API
通用代码不可接受特定平台的 API,因此如果你的代码中,直接使用了像 window
或 document
,这种仅浏览器可用的全局变量,则会在 Node.js 中执行时抛出错误,反之也是如此。
对于共享于服务器和客户端,但用于不同平台 API 的任务(task),建议将平台特定实现包含在通用 API 中,或者使用为你执行此操作的 library。例如,axios (opens new window) 是一个 HTTP 客户端,可以向服务器和客户端都暴露相同的 API。
对于仅浏览器可用的 API,通常方式是,在「纯客户端 (client-only)」的生命周期钩子函数中惰性访问 (lazily access) 它们。
请注意,考虑到如果第三方 library 不是以上面的通用用法编写,则将其集成到服务器渲染的应用程序中,可能会很棘手。你可能要通过模拟 (mock) 一些全局变量来使其正常运行,但这只是 hack 的做法,并且可能会干扰到其他 library 的环境检测代码。
自定义指令
大多数自定义指令直接操作 DOM,因此会在服务器端渲染 (SSR) 过程中导致错误。有两种方法可以解决这个问题:
推荐使用组件作为抽象机制,并运行在「虚拟 DOM 层级(Virtual-DOM level)」(例如,使用渲染函数(render function))。
如果你有一个自定义指令,但是不是很容易替换为组件,则可以在创建服务器 renderer 时,使用
directives
选项所提供"服务器端版本(server-side version)"。