Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
https://vuex.vuejs.org/zh/
vuex 是 Vue 配套的公共数据管理库,它可以把一些共享的数据,保存到 vuex 中,方便整个程序中的任何组件直接获取或修改我们的公共数据。使用Vuex的时候需要用到Vue的use方法,因此它的本质是一个插件。
store/index.js(Vuex入口文件)
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
// 用于保存全局共享数据
state: {
name: 'lnj'
},
getters: { }
// 用于同步修改共享数据
mutations: { },
// 用于异步修改共享数据
actions: { },
// 用户模块化共享数据
modules: { }
})
main.js(Vue中引入Vuex)
import Vue from 'vue'
import App from './App.vue'
import store from './store'
Vue.config.productionTip = false
new Vue({
store,
render: h => h(App)
}).$mount('#app')
通过分析它的基本结构,实现一个简易版的Vuex。
把官方的vuex替换成自己的myVuex
import Vue from 'vue'
import Vuex from './myVuex'
Vue.use(Vuex)
...
在使用Vuex的时候会通过Vuex.Store
创建一个仓库,而且为了保证每个Vue实例中都能通过this.$store
拿到仓库,还需要给每个Vue实例添加一个$store
属性。
官方初始化Store的部分代码,如下所示:
function applyMixin (Vue) {
var version = Number(Vue.version.split('.')[0]);
if (version >= 2) {
Vue.mixin({ beforeCreate: vuexInit });
} else {
var _init = Vue.prototype._init;
Vue.prototype._init = function (options) {
if ( options === void 0 ) options = {};
options.init = options.init
? [vuexInit].concat(options.init)
: vuexInit;
_init.call(this, options);
};
}
function vuexInit () {
var options = this.$options;
if (options.store) {
this.$store = typeof options.store === 'function'
? options.store()
: options.store;
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store;
}
}
}
在mvVuex.js
中添加全局$store,实现自己的Vuex
// install方法会在外界调用Vue.use的时候执行,
//并且在执行的时候会把Vue实例和一些额外的参数传递出来
const install = (vm, options)=>{
vm.mixin({
// 在初始化$options之前,给每一个Vue实例添加一个$store
beforeCreate(){
// Vue创建实例时会先创建根组件,再创建子组件,
if(this.$options && this.$options.store){
// 如果根组件,默认就有store,直接赋值即可
this.$store = this.$options.store;
}
else{
// 不是根组件,默认没有store,需要把父组件的$store赋值给它
this.$store = this.$parent.$store;
}
}
});
}
// 创建一个Store类,即初始结构
class Store {
constructor(options){
this.state = options;
}
}
export default {
install,
Store
}
Vuex的state区别与全局变量的一点,就是state是响应式的,那么如何实现响应式的state呢?
// 官方示例中,是直接重写,用Object.defineProperties实现
Object.defineProperties( Store.prototype, prototypeAccessors$1 );
重写比较麻烦的话,可以直接利用new Vue()
的时候,传入的data是响应式来实现
class Store {
constructor(options){
this.vm = new Vue({
data:{
state:options.state
}
})
}
// 自动触发get,转换this.vm,使得获取state时通过`this.$store.state`
// 而不是`this.$store.vm.state`获取state
get state(){ return this.vm.state }
}
当然,也可以使用vue中自带的方法来实现响应式
class Store {
constructor(options){
Vue.util.defineReactive(this,'state',options.state)
}
}
wrappedGetters(options){
let getters = options.getters || {};
this.getters = {};
for(let key in getters){
Object.defineProperty(this.getters, key, {
get:()=>{
return getters[key](this.state);
}
})
}
}
store/index.js文件定义getters
// store/index.js
state: {
name: 'HelloWorld'
},
getters: {
myName(state){
return '姓名:'+ state.name;
}
},
App文件中使用
// App.vue
<template>
<div class="hello">
<p>{{this.$store.getters.myName}}p>
div>
template>
wrappedMutations(options){
let mutations = options.mutations || {};
this.mutations = {};
for(let key in mutations){
this.mutations[key] = (payload)=>{ // 10
mutations[key](this.state, payload); // addNum(this.state, 10);
}
}
}
// 再实现一个commit方法用来触发Mutation,需要用箭头函数,这里的this指向state
commit=(type, payload)=>{
this.mutations[type](payload);
}
wrappedActions(options){
let actions = options.actions || {};
this.actions = {};
for(let key in actions){
this.actions[key] = (payload)=>{
actions[key](this, payload);
}
}
}
// 再实现一个dispatch方法用来触发Actions,需要用箭头函数,这里的this指向state
dispatch=(type, payload)=>{
this.actions[type](payload);
}
App.vue
<template>
<div id="app">
<p>{{this.$store.state.name}}p>
<p>{{this.$store.getters.myName}}p>
<p>{{this.$store.state.num}}p>
<p>{{this.$store.state.age}}p>
<button @click="getName">namebutton>
<button @click="myFn2">按钮button>
div>
template>
<script>
export default {
name: 'App',
mounted() {
console.log(this.$store);
},
methods:{
getName(){
this.$store.commit('addNum',10)
},
myFn2(){
this.$store.dispatch('asyncAddAge', 5);
}
}
}
script>
store/index.js
import Vue from 'vue'
import Vuex from './myVuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
name: 'HellowWorld',
num:0,
age:12
},
getters:{
myName(state){
return '姓名:'+ state.name;
}
},
mutations: {
addNum(state, payload){
state.num += payload;
},
addAge(state, payload){
state.age += payload;
}
},
actions: {
asyncAddAge({commit}, payload){
setTimeout(()=>{
commit('addAge', payload);
}, 100);
}
}
})
myVuex.js
import Vue from 'vue'
// 公共方法
function forEachValue(obj,fn){
Object.keys(obj).forEach(function (key) { return fn(obj[key], key); })
}
// install方法会在外界调用Vue.use的时候执行,
//并且在执行的时候会把Vue实例和一些额外的参数传递出来
const install = (vm, options)=>{
vm.mixin({
// 在初始化$options之前,给每一个Vue实例添加一个$store
beforeCreate(){
// Vue创建实例时会先创建根组件,再创建子组件,
if(this.$options && this.$options.store){
// 如果根组件,默认就有store,直接赋值即可
this.$store = this.$options.store;
}
else{
// 不是根组件,默认没有store,需要把父组件的$store赋值给它
this.$store = this.$parent.$store;
}
}
});
}
// 创建一个Store类,即初始结构
class Store {
constructor(options){
Vue.util.defineReactive(this,'state',options.state)
/*this.vm = new Vue({
data:{
state:options.state
}
})
*/
this.wrappedGetters(options);
this.wrappedMutations(options)
this.wrappedActions(options)
}
// 自动触发get,转换this.vm,使得获取state时通过`this.$store.state`
// 而不是`this.$store.vm.state`获取state
// get state(){ return this.vm.state }
dispatch = (type, payload)=>{
this.actions[type](payload);
}
wrappedActions(options){
let actions = options.actions || {};
this.actions = {};
forEachValue(actions,(obj,key)=>{
this.actions[key] = (payload) =>{
obj(this,payload)
}
})
}
commit = (type, payload)=>{
this.mutations[type](payload)
}
wrappedMutations(options){
let mutations = options.mutations || {};
this.mutations = {};
forEachValue(mutations,(obj,key)=>{
this.mutations[key] = (payload)=>{
obj(this.state,payload)
}
})
}
wrappedGetters(options){
let getters = options.getters || {};
this.getters = {};
forEachValue(getters,(obj,key)=>{
Object.defineProperty(this.getters,key,{
get:()=>{
return obj(this.state)
}
})
})
}
}
export default {
install,
Store
}
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module) 。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割
https://vuex.vuejs.org/zh
命名空间
多个模块中不能出现同名的getters
方法
多个模块的mutations
、actions
中可以出现同名的方法,多个同名的方法不会覆盖, 会放到数组中然后依次执行。
官方代码分析
// 自定义一个方法,根据规定的格式,来创建模块
var Module = function Module (rawModule, runtime) {
this.runtime = runtime;
this._children = Object.create(null); // 子模块
this._rawModule = rawModule; // 当前模块
var rawState = rawModule.state; // 当前模块的state
this.state = (typeof rawState === 'function' ? rawState() : rawState) || {};
};
......
var ModuleCollection = function ModuleCollection (rawRootModule) {
// 传入模块信息,传入[]空数组,代表第一次传入,还没子模块的传入
this.register([], rawRootModule, false);
};
.......
提取模块化结构,实现自己的module
let module = {
_rawModule: rootModule,
_state: rootModule.state,
_children: {}
}
在myVuex.js新增一个类,单独处理module
class ModuleCollection {
constructor(rootModule){
this.register([], rootModule);
}
register(arr, rootModule){
// console.log(arr); // [] [home] [login] [login,account]
// 1.按照需要的格式创建模块
let module = {
_rawModule: rootModule,
_state: rootModule.state,
_children: {}
}
// 2.保存模块信息
if(arr.length === 0){
// 保存根模块,第一次传入一个[]空数组,即表示根
this.root = module;
}else{
// 保存子模块,数组不为空,表示有子模块
let parent = arr.splice(0, arr.length-1).reduce((root, currentKey)=>{
return root._children[currentKey];
}, this.root);
parent._children[arr[arr.length-1]] = module;
}
// 3.处理子模块
for(let childrenModuleName in rootModule.modules){
let childrenModule = rootModule.modules[childrenModuleName];
this.register(arr.concat(childrenModuleName) ,childrenModule)
}
}
}
constructor(options){
Vue.util.defineReactive(this,'state',options.state)
this.modules = new ModuleCollection(options);
this.wrappedModules([], this.modules.root);
}
wrappedModules(arr, rootModule){
if(arr.length > 0){
let parent = arr.splice(0, arr.length-1).reduce((state, currentKey)=>{
return state[currentKey];
}, this.state);
Vue.set(parent, arr[arr.length-1], rootModule._state);
}
// 入参由之前的options,变成为现在的模块中的_rawModule
this.wrappedGetters(rootModule._rawModule);
this.wrappedMutations(rootModule._rawModule)
this.wrappedActions(rootModule._rawModule)
// 如果当前不是子模块, 那么就需要从根模块中取出子模块的信息
for(let childrenModuleName in rootModule._children){
let childrenModule = rootModule._children[childrenModuleName];
this.wrappedModules(arr.concat(childrenModuleName), childrenModule);
}
}
上面说到多个模块的mutations
、actions
中可以出现同名的方法
处理mutations
、actions
的同名方法,push到数组,逐个执行
dispatch = (type, payload)=>{
this.actions[type].forEach(fn=>fn(payload));
}
wrappedActions(options){
let actions = options.actions || {};
this.actions = this.actions || {};
for(let key in actions){
this.actions[key] = this.actions[key] || [];
this.actions[key].push((payload)=>{
actions[key](this, payload);
});
}
}
commit = (type, payload)=>{
this.mutations[type].forEach(fn=>fn(payload));
}
wrappedMutations(options){
let mutations = options.mutations || {};
this.mutations = this.mutations || {};
for(let key in mutations){
this.mutations[key] = this.mutations[key] || [];
this.mutations[key].push((payload)=>{
mutations[key](options.state, payload);
});
}
}
myVuex.js
import Vue from 'vue'
// 公共方法
function forEachValue(obj,fn){
Object.keys(obj).forEach(function (key) { return fn(obj[key], key); })
}
// install方法会在外界调用Vue.use的时候执行,
//并且在执行的时候会把Vue实例和一些额外的参数传递出来
const install = (vm, options)=>{
vm.mixin({
// 在初始化$options之前,给每一个Vue实例添加一个$store
beforeCreate(){
// Vue创建实例时会先创建根组件,再创建子组件,
if(this.$options && this.$options.store){
// 如果根组件,默认就有store,直接赋值即可
this.$store = this.$options.store;
}
else{
// 不是根组件,默认没有store,需要把父组件的$store赋值给它
this.$store = this.$parent.$store;
}
}
});
}
class ModuleCollection {
constructor(rootModule){
this.register([], rootModule);
}
register(arr, rootModule){
console.log(arr)
// 1.按照我们需要的格式创建模块
let module = {
_rawModule: rootModule,
_state: rootModule.state,
_children: {}
}
// 2.保存模块信息
if(arr.length === 0){
// 保存根模块,第一次传入一个[]空数组,即表示根
this.root = module;
}else{
// 保存子模块,数组不为空,表示有子模块
let parent = arr.splice(0, arr.length-1).reduce((root, currentKey)=>{
return root._children[currentKey];
}, this.root);
console.log(parent);
parent._children[arr[arr.length-1]] = module;
}
// // 3.处理子模块
for(let childrenModuleName in rootModule.modules){
let childrenModule = rootModule.modules[childrenModuleName];
this.register(arr.concat(childrenModuleName) ,childrenModule)
}
}
}
// 创建一个Store类,即初始结构
class Store {
constructor(options){
Vue.util.defineReactive(this,'state',options.state)
/*this.vm = new Vue({
data:{
state:options.state
}
})
*/
this.modules = new ModuleCollection(options);
this.wrappedModules([], this.modules.root);
}
wrappedModules(arr, rootModule){
if(arr.length > 0){
let parent = arr.splice(0, arr.length-1).reduce((state, currentKey)=>{
return state[currentKey];
}, this.state);
Vue.set(parent, arr[arr.length-1], rootModule._state);
}
this.wrappedGetters(rootModule._rawModule);
this.wrappedMutations(rootModule._rawModule)
this.wrappedActions(rootModule._rawModule)
// 如果当前不是子模块, 那么就需要从根模块中取出子模块的信息来安装
for(let childrenModuleName in rootModule._children){
let childrenModule = rootModule._children[childrenModuleName];
this.wrappedModules(arr.concat(childrenModuleName), childrenModule);
}
}
// 自动触发get,转换this.vm,使得获取state时通过`this.$store.state`
// 而不是`this.$store.vm.state`获取state
// get state(){ return this.vm.state }
dispatch = (type, payload)=>{
this.actions[type].forEach(fn=>fn(payload));
}
wrappedActions(options){
let actions = options.actions || {};
this.actions = this.actions||{};
forEachValue(actions,(obj,key)=>{
this.actions[key] = this.actions[key] || [];
this.actions[key].push((payload)=>{
obj(this, payload);
});
})
}
commit = (type, payload)=>{
this.mutations[type].forEach(fn=>fn(payload));
}
wrappedMutations(options){
let mutations = options.mutations || {};
this.mutations = this.mutations || {};
forEachValue(mutations,(obj,key)=>{
this.mutations[key] = this.mutations[key] || [];
this.mutations[key].push((payload)=>{
obj(options.state, payload);
});
})
}
wrappedGetters(options){
let getters = options.getters || {};
this.getters = this.getters || {};
forEachValue(getters,(obj,key)=>{
Object.defineProperty(this.getters,key,{
get:()=>{
return obj(this.state)
}
})
})
}
}
export default {
install,
Store
}
store/index.js
import Vue from 'vue'
// import Vuex from 'vuex'
import Vuex from './myVuex'
Vue.use(Vuex)
let home = {
state: {
name: 'home',
globalNum:10
},
getters: {
// 多个模块中不能出现同名的getters方法
getHomeName(state){
return state.name + '子模块Home';
},
},
mutations: {
changeHomeName(state, payload){
state.name += payload;
},
// 多个模块的mutations中可以出现同名的方法
// 多个同名的方法不会覆盖, 会放到数组中然后依次执行
changeGlobalNum(state, payload){
state.globalNum += payload;
}
},
actions: {
// 多个模块的actions中可以出现同名的方法
// 多个同名的方法不会覆盖, 会放到数组中然后依次执行
asyncChangeGlobalNum({commit}, payload){
console.log('home中的asyncChangeGlobalNum');
setTimeout(()=>{
commit('changeGlobalNum', payload);
}, 100);
}
}
}
let account = {
state: {
name: 'account',
globalNum:10
},
getters: {
// 多个模块中不能出现同名的getters方法
getAccountName(state){
return state.name + '子模块Account';
},
},
mutations: {
changeAccountName(state, payload){
state.name += payload;
},
// 多个模块的mutations中可以出现同名的方法
// 多个同名的方法不会覆盖, 会放到数组中然后依次执行
changeGlobalNum(state, payload){
state.globalNum += payload;
}
},
actions: {
// 多个模块的actions中可以出现同名的方法
// 多个同名的方法不会覆盖, 会放到数组中然后依次执行
asyncChangeGlobalNum({commit}, payload){
console.log('Account中的asyncChangeGlobalNum');
setTimeout(()=>{
commit('changeGlobalNum', payload);
}, 100);
}
}
}
let login = {
state: {
name: 'login',
globalNum:10
},
getters: {
// 多个模块中不能出现同名的getters方法
getLoginName(state){
return state.name + '子模块Login';
},
},
mutations: {
changeLoginName(state, payload){
state.name += payload;
},
// 多个模块的mutations中可以出现同名的方法
// 多个同名的方法不会覆盖, 会放到数组中然后依次执行
changeGlobalNum(state, payload){
state.globalNum += payload;
}
},
actions: {
// 多个模块的actions中可以出现同名的方法
// 多个同名的方法不会覆盖, 会放到数组中然后依次执行
asyncChangeGlobalNum({commit}, payload){
console.log('Login中的asyncChangeGlobalNum');
setTimeout(()=>{
commit('changeGlobalNum', payload);
}, 100);
}
},
modules: {
account:account
}
}
export default new Vuex.Store({
// 用于保存全局共享数据
state: {
name: 'HelloWorld',
globalNum:10,
num:0,
age:12
},
getters:{
myName(state){
return state.name + '111';
}
},
// 用于同步修改共享数据
mutations: {
addNum(state, payload){
state.num += payload;
},
addAge(state, payload){
state.age += payload;
},
changeGlobalName(state, payload){
state.globalNum += payload;
}
},
// 用于异步修改共享数据
actions: {
asyncChangeGlobalNum({commit}, payload){
console.log('全局中的asyncChangeGlobalNum');
setTimeout(()=>{
commit('changeGlobalNum', payload);
}, 100);
}
},
// 用户模块化共享数据
modules: {
home:home,
login:login
}
})
App.vue
<template>
<div id="app">
<p>父组件:{{this.$store.state.name}}p>
<p>Home子组件:{{this.$store.state.home.name}}p>
<p>Login子组件:{{this.$store.state.login.name}}p>
<p>Login中的Account子组件:{{this.$store.state.login.account.name}}p>
div>
template>