上一节,已经完成了一套完整的ssr流程,但是server-entry.js的代码还是有些不健壮
import createApp from "./app.js";
export default (context) => {
let { app, router } = createApp();
router.push(context.url); // 如果这个路由到的组件为异步组件,那么接下来的return app拿到的应用不一定会有完整的组件
// router路由对象
return app;
};
因此修改如下:
import createApp from "./app.js";
// 这里服务端渲染要求打包后的结果需要返回一个函数
export default (context) => {
// 因为有可能会是异步路由钩子函数或组件,所以我们将返回一个 Promise,
// 以便服务器能够等待所有的内容在渲染前,
// 就已经准备就绪。
return new Promise((resolve, reject) => {
const { app, router } = createApp();
router.push(context.url);
router.onReady(() => {
const matchedComponents = router.getMatchedComponents();
// 匹配不到的路由,执行 reject 函数,并返回 404
if (!matchedComponents.length) {
return reject({ code: 404 }); // 这里reject后,就会走到server.js中的render.renderToString中的reject
}
// Promise 应该 resolve 应用程序实例,以便它可以渲染
resolve(app);
}, reject);
});
};
//----------server.js
router.get("(.*)", async (ctx) => {
try {
console.log(ctx.path);
ctx.body = await new Promise((resolve, reject) => {
render.renderToString({ url: ctx.path }, (err, data) => {
if (err) {
reject(err); // 404时
} else {
resolve(data);
}
});
});
} catch (e) { //捕获到404
ctx.body = "Page Not Found";
}
});
解决问题!
下一步,添加vuex
新建store.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default () => {
let store = new Vuex.Store({
state: {
name: "",
},
mutations: {
changeName(state) {
state.name = "zf";
},
},
actions: {
changeName({ commit }) {
// 模拟的数据请求 axios
return new Promise((resolve, reject) => {
setTimeout(() => {
commit("changeName");
resolve();
}, 5000);
});
},
},
});
if (typeof window !== "undefined") {
// 服务环境没有window属性 只有客户端具备window属性
if (window.__INITIAL_STATE__) { // 这里是在server-entry中改变完state数据后, context.state自动回转化为weindow.__INITIAL_STATE__
store.replaceState(window.__INITIAL_STATE__);
}
}
return store;
};
//--------------store.js结束------------
//--------------server.entry.js
import createApp from "./app.js";
// 这里服务端渲染要求打包后的结果需要返回一个函数
// 服务端稍后会调用函数 传递一些参数到这个函数中
export default (context) => {
// 后端执行的
return new Promise((resolve, reject) => {
let { app, router, store } = createApp();
router.push(context.url);
// 等待路由中的钩子函数 执行完毕后才执行渲染逻辑
router.onReady(() => {
// 我们应该看一下 到底有没有这个路径 如果有这个路径 才去渲染
let matchComponents = router.getMatchedComponents(); // 查看是否路由对应有匹配到的组件
if (!matchComponents.length) {
return reject({ code: 404 }); // 配置404页面
}
// matchComponents 是当前路由匹配到的组件 这个组件里可能会写asyncData方法
Promise.all(
matchComponents.map((comp) => {
return comp.asyncData && comp.asyncData(store); // 这里你可能更改了状态。最终渲染的时候 我希望拿到更改的状态
})
).then(
() => {
// 默认渲染时window.__initState__
context.state = store.state; // 将刚才在服务端调用的vuex的结果放到当前的上下汶上
resolve(app);
},
(err) => {
reject(err); // 如果请求失败了 会不挂
}
);
}, reject);
});
};
//------------server-entry.js结束
//------------bar.vue----------
<template>
<div>bar {{$store.state.name}}</div>
</template>
<script>
export default {
mounted() {
// mounted 服务端不会触发此逻辑 因为服务端没有dom结构
this.$store.dispatch("changeName");
},
// ssr 只有首屏 如果已经运行起来了 后续逻辑 都是单页应用里的
asyncData(store) {
// 规定这个方法可以在后端执行 (只能在页面级组件使用)
return store.dispatch("changeName");
}
};
</script>
<style scoped>
div {
background: red;
}
</style>
网友评论