vue-ssr的实现原理连载(五):vue-ssr中的vuex
上一节 vue-ssr的实现原理连载(五):vue-ssr中的路由 中实现了vue-ssr的路由,这一节来动手实现一下vue-ssr中的store
本节的源代码可以在我的github上查看:
首先思路是和路由的一样: 创建一个store,并导出一个方法,每次调用这个方法会返回一个store实例,在main.js中创建这个实例并在Vue实例生成的时候进行注册,然后同router一同导出。
熟悉nuxt的同学知道nuxt比vue多了一部分服务端的生命周期,比如请求数据我们可以使用 fetch 或者 asyncData ,这里我们实现一下 asyncData;
首先在src下新建store.js,导出一个函数,这个函数每次调用的时候都创建一个store实例:
import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); export default () => { const store = new Vuex.Store({ state: { username: "" }, getters: { username: state => state.username }, mutations: { SET_USER_NAME(state, name) { state.username = name; } }, actions: { setUserName({ commit }) { return new Promise((resolve, reject) => { setTimeout(() => { commit("SET_USER_NAME", "Jason"); resolve(); }, 1000); }); } } }); return store; };
和路由相似,在src/main.js中引入这个store创建函数,并在Vue实例创建的时候注入
import Vue from "vue"; import App from "./App"; import createRouter from "./router"; import createStore from "./store"; export default () => { const router = createRouter(); const store = createStore(); const app = new Vue({ router, store, render: h => h(App) }); return { app, router, store }; };
在Foo页面添加上dispatch函数
<template> <div> <p>Foo页面: {{username}}</p> <button @click="handleClick">点击事件</button> </div> </template> <script> import { mapGetters } from 'vuex' export default { data() { return {}; }, computed: { ...mapGetters([ 'username' ]) }, mounted() { this.$store.dispatch('setUserName', 'Jason') }, methods: { handleClick() { alert('点击成功') } } }; </script>
然后打包客户端和服务端,然后启动node服务,打开浏览器,一切正常,但是右键查看源代码,你发现页面上并没有vuex的数据。熟悉nuxt的同学知道nuxt在页面window上挂载了一个对象,这个对象包含着首页渲染需要的数据,服务端正是从这个对象上拿到数据并渲染在页面上的。下面我来看下实现的方法;
首先,nuxt提供了一个asyncData的方法,我们先在Foo页面上添加这样一个方法:
asyncData(store) { return store.dispatch('setUserName') },
然后在server.entry.js中,当我们使用 router.getMatchedComponents() 拿到所有匹配的组件的时候,可以判断下每个组件中是否包含 asyncDate 这个方法,如果包含了就依次去执行。同时由于getMatchedComponents返回的是一组组件,而每个 asyncData 又是一个异步的方法,所以我们需要使用 Promise.all() 来等待所有的组件中的所有的异步asyncData都执行完毕然后再返回给客户端。
src/server.entry.js:
Promise.all( matchs.map(component => { if (component.asyncData) { return component.asyncData(store); } }) ).then(res => { resolve(app); });
刷新页面查看源码,可以看到页面确实渲染出了store中的数据, nuxt 中 window.__INITIAL_STATE__对象是如何生成的呢,其实很简单,我们在返回客户端之前,在context上挂载一个state属性即 store.state:
... context.state = store.state; resolve(app); ....
然后查看网页源代码:
页面上已经出现了 window.__INITIAL_STATE__对象,但是页面为什么没有显示出来store.state呢,因为store在现在服务端执行,然后渲染了页面,客户端加载页面以后,store重新执行,然后被置空了,所以我们需要在store被返回前判断环境,如果是客户端环境并且store.state被填充了之后,就使用vuex的api store.replaceState() 去替换store中的state:
if (typeof window !== "undefined" && window.__INITIAL_STATE__) { store.replaceState(window.__INITIAL_STATE__); }
保存文件,刷新浏览器,页面正常显示了。到此,vue-ssr的store也就已经完成了。
本节源码可以查看我的github: https://github.com/Jasonwang911/vue-ssr/tree/master/step5
vue-ssr的实现原理连载(一): https://www.cnblogs.com/jasonwang2y60/p/11299503.html 源码: https://github.com/Jasonwang911/vue-ssr/tree/master/step1
vue-ssr的实现原理连载(二):https://www.cnblogs.com/jasonwang2y60/p/11300255.html 源码: https://github.com/Jasonwang911/vue-ssr/tree/master/step2
vue-ssr的实现原理连载(三):https://www.cnblogs.com/jasonwang2y60/p/11300255.html 源码: https://github.com/Jasonwang911/vue-ssr/tree/master/step3
vue-ssr的实现原理连载(四):https://www.cnblogs.com/jasonwang2y60/p/11386463.html 源码: https://github.com/Jasonwang911/vue-ssr/tree/master/step4