概念
Vue 不仅可以用于客户端渲染,还可以用在服务端渲染。一个服务端的 Vue 应用被认为是同构的,因为大部分代码都会在客户端和服务端中运行。
服务端渲染相比客户端渲染有很多优势。它可以缩短首屏渲染时间,服务端生成 html 字符串后响应给浏览器,浏览器直接渲染 html 就可以看到页面内容,不需要再等待 javascript 获取和加载。它也可以提高 SEO,服务器生成 html 字符串后,爬虫就可以从中获取到网页信息。
例子
实现一个精简版的服务端渲染可以更好的理解服务端渲染的特点。
- 生成 package.json 并修改配置。初始化项目目录后, 就可以安装依赖 express 和 vue,express 用于启动一个服务器,vue 用于生成页面代码
// a. 生成 package.json
npm init -y
// b. 安装依赖
npm i express
npm i vue
// c. 修改 package.json
{
"name": "starting",
"version": "1.0.0",
"description": "",
"main": "test.js",
"type": "module",
... // 省略部分代码
"dependencies": {
"express": "^4.18.2",
"vue": "^3.2.47"
}
}
- 创建公共文件 app.js。包含数据 count 和点击修改 count 的事件,这部分代码服务端和客户端都需要。注意这里是 SSR 激活模式,所以需要使用 createSSRApp() 而不是 createApp()。
import { createSSRApp } from "vue";
export function createApp() {
return createSSRApp({
data: () => ({ count: 1 }),
template: `<div @click="count++">{{count}}</div>`,
});
}
- 客户端和服务端代码。客户端代码只需将 app.js 的内容渲染到浏览器上,而服务端代码做的事情比较多。它通过 express 创建一个服务器,响应根路由(/)的 get 请求,使用 renderToString 将组件转化成一个返回 string 的 promise,最后使用一个 html 模版渲染页面内容。
// server.js
import { renderToString } from "vue/server-renderer";
import express from "express";
import { createApp } from "./app.js";
const server = express();
server.get("/", ({ req, res }) => {
const app = createApp();
renderToString(app).then((html) => {
res.send(`
<head>
<script type="importmap">
{
"imports": {
"vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
}
}
</script>
<script type="module" src="/client.js" ></script>
</head>
<body>
<div id="app">${html}</div>
</body>
`);
});
});
server.use(express.static(".")); // 将当前目录作为静态资源的根目录
server.listen("3000", () => {
console.log("ready");
});
import { createApp } from "./app.js";
createApp().mount("#app");
- 整体流程分析。首先浏览器输入 URL 后得到 express 服务器的响应,将 server.js 生成的 html 字符串返给浏览器即可显示出页面内容。然后加载 script 脚本 client.js ,该脚本使用到了 import map 方法,从 cdn 获取到 vue 代码,再次渲染页面。这时候的页面即可以显示内容,又可以触发事件。
总结
理解服务端渲染的关键在于理解同构的思想,为了实现页面和渲染和内容可交互,把大部分代码进行了两次构建,一次是客户端,另一次和服务端。缩短了首屏渲染时间,提高 SEO,但也加大了服务端的负载。