请先阅读vue-ssr思路篇
1. 集成vue-router
配置路由信息
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
import Bar from './components/Bar.vue';
import Foo from './components/Foo.vue';
export default ()=>{
let router = new VueRouter({
mode:'history',
routes:[
{
path:'/', component:Bar,
},
{
path:'/foo',component:Foo
}
]
});
return router
}
在main中引入,将router挂载在Vue上
import Vue from 'Vue';
import App from './App';
import createRouter from './router';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
export default ()=>{
let router = createRouter();
let app = new Vue({
router,
render:(h)=>h(App)
})
return {app,router}
}
利用vue提供的渲染方法renderToString支持传递给server-entry一个对象,当请求来时,我们可以取请求的url传递进去(server.js)
router.get('/',async ctx =>{
ctx.body = await new Promise((resolve,rej)=>{
render.renderToString({url:'/'},(err,data)=>{
if(err){
rej(err);
}
resolve(data)
})
});
})
同时在返回服务器端返回vm实例前先将router进行跳转(server-entry.js)
import createApp from './main';
export default (context) => {
return new Promise((resolve, reject) =>{
const {app,router} = createApp();
router.push(context.url)
router.onReady(()=>{
let matchs = router.getMatchedComponents();
if(matchs.length === 0){
reject(404)
}
resolve(app);
},reject);
});
}
防止刷新页面不存在--配置默认路由|不存在返回404
app.use(async ctx => {
try {
ctx.body = await new Promise((resolve,rej)=>{
console.log(ctx.url);
render.renderToString({url:ctx.url},(err,data)=>{
if(err){
rej(err);
}
resolve(data)
})
});
} catch (error) {
ctx.body = '404'
}
});
2.集成vuex
配置vuex的store
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default ()=>{
let store = new Vuex.Store({
state:{
username:'wzy'
},
mutations:{
set_user(state){
state.username = 'hello'
}
},
actions:{
set_user({commit}){
return new Promise((resolve,reject)=>{
setTimeout(() => {
commit('set_user');
resolve();
}, 1000);
})
}
}
});
if(typeof window !== 'undefined' && window.__INITIAL_STATE__){
store.replaceState(window.__INITIAL_STATE__)
}
return store
}
在main中引入,将store挂载在Vue上
import Vue from 'Vue';
import App from './App';
import createRouter from './router';
import createStore from './store';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
export default ()=>{
let router = createRouter();
let store = createStore();
let app = new Vue({
router,
store,
render:(h)=>h(App)
})
return {app,router,store}
}
服务端渲染的页面,如果有数据请求的需求,则配置在组件配置asyncData方法,然后server-entry返回app之前,先执行此方法,再返回app
## Foo.vue
export default {
asyncData(store){
return store.dispatch('set_user');
},
methods:{
}
}
## server-entry.js
import createApp from './main';
export default (context) => {
return new Promise((resolve, reject) =>{
const {app,router,store} = createApp();
router.push(context.url)
router.onReady(()=>{
let matchs = router.getMatchedComponents();
if(matchs.length === 0){
reject(404)
}
Promise.all(
matchs.map(component => {
if(component.asyncData){
return component.asyncData(store)
}
})
).then(()=>{
context.state = store.state;
resolve(app)
})
},reject);
});
}
在服务端渲染时可能会执行请求数据逻辑,导致store中的state更新,所以必须通知window更新数据
- vue-ssr中会默认将server-entry.js中context的state属性值挂载到window.__INITIAL_STATE__,所以在server-entry返回app前我们可以先将新的store挂载在context.state上
- 然后可以在store中进行判断,如果存在此属性(即表明是浏览器环境),则使用store.replaceState进行替换
## server-entry.js
Promise.all(
matchs.map(component => {
if(component.asyncData){
return component.asyncData(store)
}
})
).then(()=>{
context.state = store.state;
resolve(app)
})
## store.js
if(typeof window !== 'undefined' && window.__INITIAL_STATE__){
store.replaceState(window.__INITIAL_STATE__)
}