Skip to content
On this page

vue-ssr 3.集成vuex


安装依赖

jsx
yarn add vuex@3.6.2

添加vuex相关配置

  • src/store/index.js

    jsx
    import Vuex from "vuex";
    import Vue from "vue";
    
    Vue.use(Vuex);
    
    export default function () {
    	const store = new Vuex.Store({
    		state: {
    			age: 18
    		},
    		mutations: {
    			add(state, payload) {
    				if (payload) {
    					state.age += payload;
    				} else {
    					state.age++;
    				}
    			}
    		},
    		actions: {
    			asyncAdd({ commit }, payload) {
    				return new Promise((resolve, reject) => {
    					setTimeout(() => {
    						commit('add', payload);
    						resolve();
    					}, 1000);
    				});
    			}
    		}
    	});
    
    	return store;
    }
    
  • src/app.js:引入store

    jsx
    import Vue from "vue";
    import App from "./App.vue"
    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 }
    }
    
  • src/server-entry.js:调整服务端入口

    jsx
    import createApp from "./app.js";
    
    export default function (context) {
    	const { url } = context;
    	return new Promise((resolve, reject) => {
    		const { app, router, store } = createApp();
    		//! 说明:如果当前使用的是前端路由,比如:/bar 回车就会导致404,
    		//! 解决办法:利用vue-router的push和onReady接口解决
    		//! 1. 让其跳入前端路由
    		router.push(url);
    		//! 2. 路由渲染完毕,准备进入匹配的路由
    		router.onReady(() => {
    			const matchComponents = router.getMatchedComponents();
    			if (matchComponents.length === 0) {
    				return reject({ code: 404 });
    			} else {
    				Promise.all(matchComponents.map(component => {
    					//! 1. 调用异步组件的asyncData 函数,进行异步数据获取
    					if (component.asyncData) {
    						console.log("execute: component asyncData");
    						return component.asyncData(store);
    					}
    				})).then(() => {
    					//! 2. 等待异步组件数据全部处理完毕后,需要将后端的store.state 挂载至上下文中,方便客户端对服务端的state数据进行同步
    					context.state = store.state;
    					resolve(app);
    				});
    			}
    		});
    	});
    }
    

页面使用store

  • Foo.vue

    jsx
    <template>
    	<div>Foo page age: {{ $store.state.age }}</div>
    </template>
    
    <script>
    export default {
    	name: "Foo",
    	/* mounted() {
    		this.$store.dispatch("asyncAdd", 1000);
    	}, */
    	asyncData(store) {
    		return store.dispatch("asyncAdd", 1000);
    	},
    };
    </script>
    
    <style scoped>
    div {
    	background: red;
    }
    </style>
    

至此vuex 集成完毕,但是会存在一个问题服务端将数据state.age渲染成1018,但是前端页面还是显示的age: 18

解决:客户端与服务端store.state不同步,导致页面数据闪回原来的样子的问题

  • src/store/index.js

    jsx
    import Vuex from "vuex";
    import Vue from "vue";
    
    Vue.use(Vuex);
    
    export default function () {
    	const store = new Vuex.Store({
    		state: {
    			age: 18
    		},
    		mutations: {
    			add(state, payload) {
    				if (payload) {
    					state.age += payload;
    				} else {
    					state.age++;
    				}
    			}
    		},
    		actions: {
    			asyncAdd({ commit }, payload) {
    				return new Promise((resolve, reject) => {
    					setTimeout(() => {
    						commit('add', payload);
    						resolve();
    					}, 1000);
    				});
    			}
    		}
    	});
    
    	//! 用后端最新的store.state 替换掉client中的store.state 避免服务端已渲染最新的state 页面后,client又渲染会之前的state中的状态页面效果
    	//! 注意:此时需要用global变量,webpack打包后会自动替换global变量(不要使用window会导致报错)
    	if (global && global.__INITIAL_STATE__) {
    		store.replaceState(global.__INITIAL_STATE__);
    	}
    
    	return store;
    }
    

示例代码:传送门

总结

  • 等页面和异步数据处理完后,需将server端store.state 挂载至context对象中,context.state = store.state(原因:让客户端同步store.state数据事从context获取)
  • store创建完后,从global.__INITIAL_STATE__查看是否有server挂载的state数据,如果存在的话就需要将server state 数据替换client state数据

以上:如发现有问题,欢迎留言指出,我及时更正

Released under the MIT License.