本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2021-02-26
全部摘抄于:Vue SSR 指南
可以直接在创建 renderer 时提供一个页面模板。多数时候,我们会将页面模板放在特有的文件中,例如 index.template.html:
<!DOCTYPE html>
<html lang="en">
<head><title>Hello</title></head>
<body>
<!--vue-ssr-outlet-->
</body>
</html>
注意 <!--vue-ssr-outlet-->
注释,这里将是应用程序 HTML 标记注入的地方。
const renderer = require('vue-server-renderer').createRenderer({
template: require('fs').readFileSync('./index.template.html', 'utf-8')
})
renderer.renderToString(app, (err, html) => {
console.log(html) // html 将是注入应用程序内容的完整页面
})
<!-- index.template.html -->
<html>
<head>
<!-- 使用双花括号进行 HTML 转义插值 -->
<title>{{ title }}</title>
<!-- 使用三花括号进行 HTML 不转义插值 -->
{{{ meta }}}
</head>
<body>
<!--vue-ssr-outlet-->
</body>
</html>
const context = {
title: 'hello',
meta: `
<meta ...>
<meta ...>
`
}
renderer.renderToString(app, context, (err, html) => {
// 页面 title 将会是 "Hello"
// meta 标签也会注入
})
当运行在不同环境中时,我们的代码将不会完全相同。所以这里我们将会阐述你需要理解的关键事项。
1、对于服务器端渲染,我们应该使得每个请求应该都是全新的、独立的应用程序实例,以便不会有交叉请求造成的状态污染。
Node.js服务器是一个长期运行的进程。当我们的代码进入该进程时,它将进行一次取值并留存在内存中。这意味着如果创建一个单例对象,它将在每个传入的请求之间共享。所以我们为每个请求创建一个新的根 Vue 实例以避免交叉请求状态污染,因此,我们不应该直接创建一个应用程序实例,而是应该暴露一个可以重复执行的工厂函数,为每个请求创建新的应用程序实例。
由于没有动态更新(在服务端处理完之后发送给前端的就是首页完整的HTML文档了,不需要$mount
挂载),所有的生命周期钩子函数中,只有 beforeCreate
和 created
会在服务器端渲染过程中被调用。这就是说任何其他生命周期钩子函数中的代码只会在客户端执行。
2、此外还需要注意的是,你应该避免在 beforeCreate
和 created
生命周期时产生全局副作用的代码。
例如在其中使用 setInterval 设置 timer。在纯客户端的代码中,我们一般在 beforeDestroy
或 destroyed
生命周期时将其销毁。但是,由于在 SSR 期间并不会调用销毁钩子函数,所以 timer 将永远保留下来。为了避免这种情况,请将副作用代码移动到 beforeMount
或 mounted
生命周期中。
3、通用代码不可接受特定平台的 API,因此如果你的代码中,直接使用了像 window 或 document,这种仅浏览器可用的全局变量,则会在 Node.js 中执行时抛出错误,反之也是如此。
建议将平台特定实现包含在通用 API 中,或者使用可以向服务器和客户端暴露相同API的一些第三方库,例如 axios;对于仅浏览器可用的 API,通常方式是,在纯客户端的生命周期钩子函数中惰性访问它们(交由页面加载完成后,应用被激活的时候来处理)。
4、大多数自定义指令直接操作 DOM,因此会在服务器端渲染 (SSR) 过程中导致错误。有两种方法可以解决这个问题:
推荐使用组件作为抽象机制,并运行在虚拟 DOM 层级(例如,使用渲染函数)。
如果你有一个自定义指令,但是不是很容易替换为组件,则可以在创建服务器 renderer 时,使用 createRenderer({ directives })
选项所提供"服务器端版本"。
v-show 的服务器端版本示例:
export default function show (node: VNodeWithData, dir: VNodeDirective) {
if (!dir.value) {
const style: any = node.data.style || (node.data.style = {})
if (Array.isArray(style)) {
style.push({ display: 'none' })
} else {
style.display = 'none'
}
}
}