数据预取
数据预取的关键在于,在服务器端渲染过程中,提前获取并处理好组件所需的任何动态数据。这些数据可以来自 API 请求、数据库查询或其他任何形式的数据源。通过这种方式,可以在生成 HTML 之前填充好所有的内容,避免用户看到空白或加载中的页面。
创建仓库
在 store 目录下创建一个 index.js 文件,并定义一个名为 createStore 的函数,返回一个 Vuex Store 实例。
// src/store/index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export function createStore() {
return new Vuex.Store({
state: {
name: "",
},
mutations: {
setName(state, payload) {
state.name = payload;
},
},
actions: {
getName({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("SSR");
}, 300);
}).then((res) => {
commit("setName", res);
});
},
},
});
}组件中使用
在组件中,你可以通过 this.$store.state 访问到预取的数据。例如:
<!-- src/components/Demo.vue -->
<template>
<div class="demo">
<h1>欢迎,{{ name }}</h1>
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
name: "HelloWorld",
props: {
msg: String,
},
computed: {
...mapState(["name"]),
},
created() {
this.$store.dispatch("getName");
},
};
</script>main.js 文件调整
// src/main.js
import Vue from "vue";
import App from "./App.vue";
import createStore from "./store";
export default function () {
const store = createStore();
const app = new Vue({
render: (h) => h(App),
store, // 将 store 注入到 Vue 实例中
});
return { app, store };
}组件中定义 asyncData 方法
你可以在每个需要预取数据的 Vue 组件中定义一个静态方法 asyncData。这个方法会在组件被匹配到路由时调用,并返回一个 Promise 或者直接返回对象。例如:
import { mapState } from "vuex";
export default {
data() {
return {};
},
computed: {
...mapState(["name"]),
},
asyncData({ store, route }) {
// 这里假设有一个 Vuex action 可以获取数据
return store.dispatch("getName");
},
// ...
};服务器端
服务器端入口文件 entry-server.js
回到服务器端入口文件 (entry-server.js),我们需要确保在渲染之前完成了所有组件的数据预取。这部分代码会遍历所有匹配的组件,并调用它们的 asyncData 方法。一旦所有的数据都已经被获取,我们就继续渲染应用程序。
import createApp from "../main.js";
export default (context) => {
return new Promise((resolve, reject) => {
const { app, router, store } = createApp();
router.push(context.url);
// 等待所有异步组件和钩子完成
router.onReady(async () => {
const matchedComponents = router.getMatchedComponents();
if (!matchedComponents.length) {
reject({ code: 404 });
}
// 对匹配的组件预取数据
const promises = matchedComponents.map((Component) => {
if (Component.asyncData) {
return Component.asyncData({
store,
route: router.currentRoute.value,
});
}
});
// 如果有异步数据需要获取,则等待所有请求完成
if (promises.length) {
await Promise.all(promises);
}
context.state = store.state;
resolve(app);
});
});
};这段代码首先检查是否有任何匹配的组件实现了 asyncData 方法,然后使用 Promise.all() 来并发地执行所有的数据获取操作。这样可以确保所有必要的数据都在渲染前准备好。
服务端主文件 server/index.js
客户端 client-entry.js
为了让客户端也能够访问这些数据,在服务器端渲染完成后,你可以将这些数据作为全局变量注入到 HTML 模板中。这通常是在模板中添加一段 <script> 标签来完成的:
<!-- 通过“显示页面源码”操作可以看到注入的代码 -->
<script>
window.__INITIAL_STATE__ = {
/* 预取的数据 */
};
</script>这段脚本会在页面加载时被执行,将服务器端获取的数据赋值给一个全局变量 window.__INITIAL_STATE__。
然后在客户端初始化 Vuex Store 时读取并合并这些初始状态:
import createApp from "../main.js";
//
const { app, store } = createApp();
app.$mount("#app");
if (window.__INITIAL_STATE__) {
store.replaceState(window.__INITIAL_STATE__);
}优化与进阶
错误处理:确保在数据预取过程中正确处理可能发生的错误,比如网络问题或者 API 返回无效数据。
缓存策略:对于频繁访问且变化不大的数据,考虑在服务器端实现缓存机制,减少不必要的 API 调用。
代码分割:利用 Webpack 的动态导入功能对不同的路由进行代码分割,只加载当前页面所需的模块,提升性能。
条件性数据预取:并非所有页面都需要预取数据,可以根据具体情况决定是否执行
asyncData方法,避免不必要的计算开销。
总结
通过上述步骤,你可以在 Vue SSR 环境中有效地实现数据预取。这种方法不仅有助于提高首屏加载速度,还能够为用户提供更流畅的交互体验。