vue-ssr | 具体实现篇 | 集成vuex及vue-router

1,809 阅读2分钟
请先阅读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__)
    }