高级 在客户端,我们需要创建一个和服务器端相同的Vue实例,并挂载到同一个元素上。这个过程称为混合(hydration),可以使客户端接管服务器端生成的静态内容,变为可交互的应用。
详细教程
@click="count++">{{ count }}</button>`
})
renderToString(app).then((html) => {
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>Vue SSR Example</title>
</head>
<body>
<div id="app">${html}</div>
</body>
</html>
`)
})
})
server.listen(3000, () => {
console.log('ready')
})最后,执行 node server.js,访问 http://localhost:3000。你应该可以看到页面中的按钮了。
在 StackBlitz 上试试
客户端激活
如果你点击该按钮,你会发现数字并没有改变。这段 HTML 在客户端是完全静态的,因为我们没有在浏览器中加载 Vue。
为了使客户端的应用可交互,Vue 需要执行一个激活步骤。在激活过程中,Vue 会创建一个与服务端完全相同的应用实例,然后将每个组件与它应该控制的 DOM 节点相匹配,并添加 DOM 事件监听器。
为了在激活模式下挂载应用,我们应该使用 createSSRApp() 而不是 createApp():
// 该文件运行在浏览器中
import { createSSRApp } from 'vue'
const app = createSSRApp({
// ...和服务端完全一致的应用实例
})
// 在客户端挂载一个 SSR 应用时会假定
// HTML 是预渲染的,然后执行激活过程,
// 而不是挂载新的 DOM 节点
app.mount('#app')代码结构
想想我们该如何在客户端复用服务端的应用实现。这时我们就需要开始考虑 SSR 应用中的代码结构了——我们如何在服务器和客户端之间共享相同的应用代码呢?
这里我们将演示最基础的设置。首先,让我们将应用的创建逻辑拆分到一个单独的文件 app.js 中:
// app.js (在服务器和客户端之间共享)
import { createSSRApp } from 'vue'
export function createApp() {
return createSSRApp({
data: () => ({ count: 1 }),
template: `<button @click="count++">{{ count }}</button>`
})
}该文件及其依赖项在服务器和客户端之间共享——我们称它们为通用代码。编写通用代码时有一些注意事项,我们将在下面讨论。 我们在客户端入口导入通用代码,创建应用并执行挂载:
// client.js
import { createApp } from './app.js'
createApp().mount('#app')服务器在请求处理函数中使用相同的应用创建逻辑:
// server.js (不相关的代码省略)
import { createApp } from './app.js'
server.get('/', (req, res) => {
const app = createApp()
renderToString(app).then(html => {
// ...
})
})此外,为了在浏览器中加载客户端文件,我们还需要:
- 在
server.js中添加server.use(express.static('.'))来托管客户端文件。 - 将
<script type="module" src="/client.js"></script>添加到 HTML 外壳以加载客户端入口文件。 - 通过在 HTML 外壳中添加 Import Map 以支持在浏览器中使用
import * from 'vue'。 在 StackBlitz 上尝试完整的示例。按钮现在可以交互了!
原文
实现混合的具体方案
- child::实现混合