浅谈vuex源码分析

本文详细探讨Vuex的初始化过程,包括Vue.use(Vuex)的执行、Store构造函数中模块的创建与安装,以及如何通过installModule方法构建模块收集器对象。还介绍了Vuex的getter、mutation、action的处理,动态注册模块,插件的定义和使用,以及mapState等辅助函数的工作原理。总结了Vuex如何维护store对象,并确保状态管理的安全和高效。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

vuex使用方式

store/index.js

import Vue from 'vue'
import Vuex from '@/vuex'
// import Vuex from 'vuex'
// import logger from 'vuex/dist/logger.js'
Vue.use(Vuex)
// // new Vue

let store = new Vuex.Store({ 
    state: { // state = > data
        name: 'zhufeng',
        age: 12
    },
    mutations: { // method  commit 同步更改状态
        changeAge(state, payload) {
            state.age += payload
        }
    },
    actions: { // 异步操作 调用api接口 dispatch, 多次commit mutation  
        changeAge({ commit }, payload) {
            setTimeout(() => {
                commit('changeAge', payload);
            }, 1000);
        }
    },
    getters: { // 计算属性
        myAge(state) {
            return state.age + 10
        }
    },
    strict: true, // 如果不是在mutation中操作的状态会发生警告
    modules: { // 进行模块分割
        // namespaced 能解决子模块和父模块的命名冲突文件 ,相当于增加了一个命名空间
        // 如果没有namespaced 默认getters都会被定义到父模块上,
        // mutations 会被合并在一起, 最终一起调用,有了命名空间就没有这个问题了
        // 子模块的名字不能和父模块中的状态重名
        a: {
            namespaced: true,
            state: {
                name: 't1',
                age: 10
            },
            // 所有的getters 都会被合并到跟上
            getters: { // 首页一个模块 home 订单页一个模块 order  用户一个模块 user
                myAge(state) {
                    return state.age + 20;
                }
            },
            mutations: {
                changeAge(state, payload) {
                    state.age += payload
                }
            },
            modules: {
                c: {
                    namespaced: true,
                    state: {
                        age: 100
                    },
                    mutations: {
                        changeAge(state, payload) {
                            state.age += payload
                        }
                    },
                    modules: {
                        d: {
                            namespaced: true,
                            state: {
                                age: 100
                            },
                        }
                    }
                }
            }
        },

    }
})

export default store;

1、 初始化

一开始Vue.use(Vuex): Vue.use就是使执行vuex的install方法
Vuex里面有个install方法, 用于执行install()

install.js

export let Vue;
function install(_Vue) {
    Vue = _Vue;
    Vue.mixin({
        beforeCreate(){ //this代表的是每个组件实例
            // 获取根组件上的store 将他共享给每个组件
            // 每个组件中都应该有$store
            let options= this.$options;
            if(options.store){
                // 根
                // console.log('根',options.name)
                this.$store = options.store
            }else{
                // 先保证他是一个子组件,并且父亲上有$store
                if(this.$parent && this.$parent.$store){
                    this.$store = this.$parent.$store
                }
            }
        }
    })
} 
// 父  this.$store -》 子 this.$store -》孙子 this.$store
export default install

install方法: 传入了_Vue, 是为了拿到全局vue对象,统一vue版本, 同时执行minxin方法,各个组件等到执行beforeCreate生命周期时:给每个vue实例获取根对象的store,每个vue实例就有$store对象了。

2、创建store对象 — 创建模块收集器对象

let store = new Vuex.Store({ 
  ...
})

这一步说明了Vuex.Store是一个构造函数

vuex/store.js

class Store {
    constructor(options) {
        // 对用户的模块进行整合 
        // 当前格式化完毕的数据 放到了this._modules里
        this._modules = new ModuleCollection(options); // 对用户的参数进行格式化操作
        this.wrapperGetters = {}
        // 我需要将模块中的所有的getters,mutations,actions进行收集
        this.mutations = {};
        this.actions = {};
        this._subscribes = [];
        this._committing = false; // 默认不是在mutation中更改的

        this.strict = options.strict;


        // 没有namespace的时候 getters都放在根上 ,actions,mutations 会被合并数组
        let state = options.state;

        installModule(this, state, [], this._modules.root);

        resetVM(this, state);
        if (options.plugins) { // 说明用户使用了插件
            options.plugins.forEach(plugin => plugin(this))
        }
    }
    _withCommittting(fn) {
        this._committing = true; // 如果true
        fn(); // 函数是同步的 获取_commiting 就是true,如果是异步的那么就会变成false 就会打印日志
        this._committing = false;
    }
    subscribe(fn) {
        this._subscribes.push(fn);
    }
    replaceState(newState) { // 需要替换的状态

        this._withCommittting(() => {
            this._vm._data.$$state = newState; // 替换最新的状态, 赋予对象类型会被重新劫持
        })

        // 虽然替换了状态,但是mutation getter中的state在初始化的时候 已经被绑定死了老的状态
    }

    get state() {
        return this._vm._data.$$state
    }
    commit = (mutationName, payload) => { // 发布
        this.mutations[mutationName] && this.mutations[mutationName].forEach(fn => fn(payload))
    }
    dispatch = (actionName, payload) => {
        this.actions[actionName] && this.actions[actionName].forEach(fn => fn(payload))
    }

    registerModule(path, module) { // 最终都转换成数组  register(['a','c'])
        if (typeof path == 'string') path = [path];

        // module 是用户直接写的
        this._modules.register(path, module); // 模块的注册, 将用户给的数据放到树中
        // 注册完毕后 ,在进行安装

        // 将用户的module 重新安装
        installModule(this, this.state, path, module.newModule);

        // vuex内部重新注册的话 会重新生成实例, 虽然重新安装了 ,只解决了状态的问题,但是computed就丢失了
        resetVM(this, this.state); // 销毁重来
    }
}

一开始 this._modules = new ModuleCollection(options);

ModuleCollection是一个模块收集器:传入用户定义的所有选项options。

vuex/module/module-collection.js

import { forEach } from '../util'
import Module from './module';
class ModuleCollection {
    constructor(options) {
        // 对数据进行格式化操作
        this.root = null;
        this.register([],options); // 为了记录父子关系 
    }
    getNamespace(path){ // [a,b,c]
        // 返回一个字符串 a/b/c   ''
        let root = this.root
        let ns =  path.reduce((ns,key)=>{ // this.root.c.namespace
           let module =  root.getChild(key); 
           root = module;
           return module.namespaced ? ns + key + '/'   :ns
        },'');
        return ns;
    }
    register(path,rawModule){ 
        let newModule = new Module(rawModule)
        rawModule.newModule = newModule;// 自定义属性
        if(path.length == 0){
            this.root = newModule
        }else{
            // [a] // [a,c]  => [a]
            // 找父亲
            let parent = path.slice(0,-1).reduce((memo,current)=>{
                return memo.getChild(current)
            }, this.root);
            parent.addChild(path[path.length-1],newModule);
            // 根据当前注册的key ,将他注册到对应的模块的儿子处
        }
        // 注册完毕当前模块,在进行注册子模块 
        if(rawModule.modules){
            forEach(rawModule.modules,(module,key)=>{
               this.register(path.concat(key),module);
            })
        }
    }
}

export default ModuleCollection

new ModuleCollection(options) 执行 this.register([],options);

然后 let newModule = new Module(rawModule) ,

展开Module构造函数看看:
vuex/module/module.js

import { forEach } from '../util'
class Module {
    constructor(rawModule) {
        this._raw = rawModule;
        this._children = {};
        this.state = rawModule.state
    }
    getChild(childName) {
        return this._children[childName]
    }
    addChild(childName, module) {
        this._children[childName] = module
    }
    forEachGetter(cb) {
        this._raw.getters && forEach(this._raw.getters, cb)
    }
    forEachMutation(cb) {
        this._raw.mutations && forEach(this._raw.mutations, cb)
    }
    forEachAction(cb) {
        this._raw.actions && forEach(this._raw.actions, cb)
    }
    forEachChildren(cb) {
        this._children && forEach(this._children, cb)
    }
    // 用于标识他自己是否写了namesapced
    get namespaced(){ // module.namespaced
        return !!this._raw.namespaced
    }
}
export default Module

一个module就是包含state 、_raw(整个module对象属性)、_children对象, 同时加上一些操作children、actions、getters的方法。把对象级别的rawModule格式化成一个可以操作函数的newModule。
这里就是new了一个module而已,方便后续函数操作。

接着看回来module-collection.js 用 rawModule.newModule = newModule; 标记这个新的newModule

  if (path.length == 0){
        this.root = newModule
    }else{
        // [a] // [a,c]  => [a]
        // 找父亲
        let parent = path.slice(0,-1).reduce((memo,current)=>{
                return memo.getChild(current)
        }, this.root);
        parent.addChild(path[path.length-1],newModule);
       // 根据当前注册的key ,将他注册到对应的模块的儿子处
    }

如果path = [] 即 path.length === 0 就是根module, 用 this.root = newModule即可。
如果不是根module, 即path = [a] 或者 [a,c]
那么就会按照路径往上一级查找父module
比如[a] 就会找到根module [a,c]则会找到a的module
找到这个父module后 就执行addChild,传入模块名字和模块即可

addChild(childName, module) {
        this._children[childName] = module
    }

此时父module的_children对象有个a属性的module

接着往下走

   // 注册完毕当前模块,在进行注册根模块 
        if(rawModule.modules){
            forEach(rawModule.modules,(module,key)=>{
               this.register(path.concat(key),module);
            })
        }

如果原始传入的rawModule有modules属性。 就遍历modules 然后再执行this.register() 注册子module

new ModuleCollection(options) 最终目的就是创建一个模块收集的对象,
这个对象里面包含了root对象, root对象包含父子嵌套关系的所有用户传入的格式化后的modules.
格式化后的modules. 里面有自己模块的state _raw(原始对象) _children
root对象:
在这里插入图片描述

3、创建store对象 — 安装模块installModule

接着看回来vuex/store.js,执行installModule, 传入的参数 store对象, options.state, 根模块名[],模块收集器的root对象

 installModule(this, state, [], this._modules.root);

这个方法展开看看

function installModule(store, rootState, path, module) { //  a/b/c/d
    // 需要循环当前模块的

    // 获取moduleCollection类的实例
    let ns = store._modules.getNamespace(path);
    // module.state => 放到rootState对应的儿子里
    if (path.length > 0) { // 儿子模块 
        // 需要找到对应父模块,将状态声明上去
        // {name:'zf',age:'12',a:aState}
        let parent = path.slice(0, -1).reduce((memo, current) => {
            return memo[current];
        }, rootState);
        // 对象新增属性不能导致重新更新视图
        store._withCommittting(() => {
            Vue.set(parent, path[path.length - 1], module.state);
        })
    }
    module.forEachGetter((fn, key) => {
        store.wrapperGetters[ns + key] = function() {
            return fn.call(store, getNewState(store, path));
        }
    });
    module.forEachMutation((fn, key) => { // {myAge:[fn,fn]}
        store.mutations[ns + key] = store.mutations[ns + key] || [];
        store.mutations[ns + key].push((payload) => {
            store._withCommittting(() => {
                fn.call(store, getNewState(store, path), payload); // 先调用mutation 在执行subscirbe
            })

            store._subscribes.forEach(fn => fn({ type: ns + key, payload }, store.state));
        })
    });
    module.forEachAction((fn, key) => {
        store.actions[ns + key] = store.actions[ns + key] || [];
        store.actions[ns + key].push((payload) => {
            return fn.call(store, store, payload)
        })
    });
    module.forEachChildren((child, key) => {
        installModule(store, rootState, path.concat(key), child);
    });
}

一开始执行
store._modules就是模块收集器对象
let ns = store._modules.getNamespace(path);

 getNamespace(path){ // [a,b,c]
        // 返回一个字符串 a/b/c   ''
        let root = this.root
        let ns =  path.reduce((ns,key)=>{ // this.root.c.namespace
           let module =  root.getChild(key); 
           root = module;
           return module.namespaced ? ns + key + '/'   :ns
        },'');
        return ns;
    }

根modules情况下 传入的path = [] 所以这里返回ns = ‘’
如果传入的path = [a,b],且namespaced = true 那么返回 a/b/

接着执行module.forEachGetter

module.forEachGetter((fn, key) => {
        store.wrapperGetters[ns + key] = function() {
            return fn.call(store, getNewState(store, path));
        }
    });

fn就是各个定义的getter函数, key就是getter函数名字, 大致就是把各个getter函数转为下面格式

store.wrapperGetters.myAge = function() {
     return myAge(getNewState(store, path));
}

getNewState(store, path) 就是返回对应path下的state, 比如[a,b] 就是返回b下面的state

function getNewState(store, path) {
    return path.reduce((memo, current) => {
        return memo[current];
    }, store.state)
}

接着执行 module.forEachMutation

 module.forEachMutation((fn, key) => { // {myAge:[fn,fn]}
        store.mutations[ns + key] = store.mutations[ns + key] || [];
        store.mutations[ns + key].push((payload) => {
            store._withCommittting(() => {
                fn.call(store, getNewState(store, path), payload); // 先调用mutation 在执行subscirbe
            })
            store._subscribes.forEach(fn => fn({ type: ns + key, payload }, store.state));
        })
    });

上面 mutations为什么是一个数组? 可能会有多个模块使用相同mutations的名字而且没有添加namespaced: true,那么就会有是一个数组包含多个mutations函数。
比如commit(‘changeAge’,10)就会触发多个不同模块的mutation的changeAge(假如没有命名空间)

这里以数组形式添加了各个mutations, 然后用store._withCommittting函数包裹。
_withCommittting函数如下 , 为了_committing = true状态后再去执行函数,严格模式下, 更好的防止用户其他方式直接修改了state, 必须要commit方式修改state

 _withCommittting(fn) {
        this._committing = true; // 如果true
        fn(); // 函数是同步的 获取_commiting 就是true,如果是异步的那么就会变成false 就会打印日志
        this._committing = false;
    }

同时执行store._subscribes队列里面的函数,他们是订阅时添加进去的。

接着执行 module.forEachAction

module.forEachAction((fn, key) => {
        store.actions[ns + key] = store.actions[ns + key] || [];
        store.actions[ns + key].push(payload => {
            return fn.call(store, store, payload);
        });
    });

同理 mutations, action也是一个数组维护,

通过上面可以看出getter mutation 和 action都是放在顶层的一个store对象下面的, 如果是子modules, 并且有命名空间就会用命名空间再做一层隔离存放而已。

接着执行子模块的安装

 module.forEachChildren((child, key) => {
        installModule(store, rootState, path.concat(key), child);
    });

子模块遍历时
执行installModule 子模块安装, 传入的path 是父级的path加上模块名字key 比如 一开始顶层path[] 然后子模块是a 那就是[a],
child就是该子模块

重新进入installModule

if (path.length > 0) {
        // 儿子模块
        // 需要找到对应父模块,将状态声明上去
        // {name:'zf',age:'12',a:aState}
        let parent = path.slice(0, -1).reduce((memo, current) => {
            return memo[current];
        }, rootState);
        // 对象新增属性不能导致重新更新视图
        store._withCommittting(() => {
            Vue.set(parent, path[path.length - 1], module.state);
        });
    }

此时 path.length > 0
根据路径找到父级state -> parent
然后执行Vue.set(parent, path[path.length - 1], module.state);
比如 父级state.a = 子模块的state
也就是说此时父级的module就有个a 记录该a模块的state

比如 rootState = { 
        name : 1, 
        a: { 
            age: 2,
            b: {
                sex: 'man'
               }
            }
        }

父子级别state就直接维护在父子对应的对象里面。

紧接着如果再有子模块继续遍历安装子模块,直到无子模块需要安装了。

这一大步骤 最终整合了所有模块的mutations actions wrapperGetters到 store对象下面,会根据命名空间需要,组合key再命名。可以说全部子模块的方法都挂在store这一大对象里面。
同时根据各个模块的state , 整合成一个大的state, 通过模块名字连接各个子模块的state.

整个state整合了如下
在这里插入图片描述
store对象整合了所有子模块的函数了:
在这里插入图片描述

4、installModule完毕后,就执行resetVM(this, state);

function resetVM(store, state) {
    let oldVm = store._vm;
    store.getters = {};
    const computed = {};
    forEach(store.wrapperGetters, (getter, key) => {
        computed[key] = getter;
        Object.defineProperty(store.getters, key, {
            get: () => store._vm[key]
        });
    });
    store._vm = new Vue({
        data: {
            $$state: state
        },
        computed
    });
    if (store.strict) {
        // 说明是严格模式我要监控状态
        store._vm.$watch(
            () => store._vm._data.$$state,
            () => {
                // 我希望状态变化后 直接就能监控到,watcher都是异步的? 状态变化会立即执行,不是异步watcher
                console.assert(
                    store._committing,
                    'no mutate in mutation handler outside'
                );
            },
            { deep: true, sync: true }
        ); // 内部会遍历所有的属性
    }
    if (oldVm) {
        // 重新创建实例后,需要将老的实例卸载掉
        Vue.nextTick(() => oldVm.$destroy());
    }
}

4.1 先记录oldVm 后面用到
4.2 store对象里面加一个getters属性
4.3 然后遍历store.wrapperGetters , 把里面的函数放到computed对象, 然后store.getters.key 指向 store._vm.key
也就是说把getter的属性取值,映射到computed取值
4.4 store._vm就是一个vue实例, computed指向getter函数。

 store._vm = new Vue({
        data: {
            $$state: state
        },
        computed
    });

4.5 如果是严格模式 store.strict = true;
监听$$state的属性, state属性变化, 就会触发console.assert函数, 如果store._committing = false, 就打印报错信息, 如果是true, 就不打印。

if (store.strict) {
        // 说明是严格模式我要监控状态
        store._vm.$watch(
            () => store._vm._data.$$state,
            () => {
                // 我希望状态变化后 直接就能监控到,watcher都是异步的? 状态变化会立即执行,不是异步watcher
                console.assert(
                    store._committing,
                    'no mutate in mutation handler outside'
                );
            },
            { deep: true, sync: true }
        ); // 内部会遍历所有的属性
    }

4.6 如果有oldVm, 说明是至少第二次进入了这个函数,而且是有旧的vue实例, 故卸载掉旧的oldVm.$destroy()。

if (oldVm) {
        // 重新创建实例后,需要将老的实例卸载掉
        Vue.nextTick(() => oldVm.$destroy());
    }

5、调用插件

如果有插件这个参数 就遍历插件 执行插件函数,然后传入store对象

if (options.plugins) {
            // 说明用户使用了插件
            options.plugins.forEach(plugin => plugin(this));
        }

6、页面使用vuex的state

 
 <template>
  <div id="app">
    学校的年龄{{this.$store.state.name}} {{this.$store.state.age}} <br>
    我的年龄是 {{this.$store.getters.myAge}} 
    我的年龄是 {{this.$store.getters.myAge}} 
    
    <button @click="$store.commit('changeAge',10)">更改年龄</button>
    <button @click="$store.dispatch('changeAge',10)">异步年龄</button>

    <hr>

   t1的年龄 {{this.$store.state.a.name}}  {{this.$store.state.a.age}} <br>

   t1的计算年龄  {{this.$store.getters['a/myAge']}}
  <button @click="$store.commit('a/changeAge',10)">t1更改年龄</button>
  <hr>

  c的年龄 {{this.$store.state.a.c.age}}
  <button @click="$store.commit('a/c/changeAge',10)">t1更改年龄</button>
  </div>
</template>



<script>
export default {
  name:'app',
 
}
</script>

<style>

</style>


this.$store.state.name 
this.$store.state.age 
因为每个实例都有了$store 故每个组件可以直接this.$store取值

store对象 有个state取值函数, 实际就是取_vm的_data.$$state

 get state() {
        return this._vm._data.$$state;
    }

这些变量就是直接取$store.state对象里面的就可以了, 不管有无namespaces=true或者false的命名空间, state都是通过模块名去区分

取a模块的name 就直接取 this.$store.state.a.name  
取a模块的c模块 则取this.$store.state.a.c.name

而修改state 我们通过$store.commit函数触发

$store.commit('changeAge', 10)

commit函数实现
vuux/store.js 里面查看

commit = (mutationName, payload) => {
        // 发布
        this.mutations[mutationName] &&
            this.mutations[mutationName].forEach(fn => fn(payload));
    };

实际就是去store.mutations.changeAge这个数组属性里面看看有无函数,有函数则遍历执行,传入payload就是了, 就会去找用户定义的这个mutation函数执行。

如果需要按照修改模块里面的state,且有命名空间,则需要根据commit( ‘模块名字’ + ‘/’ + mutationName)触发修改

<button @click="$store.commit('a/changeAge', 10)">t1更改年龄</button>

比如 上面就是触发a模块的’changAge’ mutation

同理getters属性也是依据是否有命名空间, 加上比如 ‘a/’ + 取值state

this.$store.getters["a/myAge"] 

而dispatch 可以实现异步commit state

$store.dispatch('changeAge', 10)
actions: { // 异步操作 调用api接口 dispatch, 多次commit mutation  
        changeAge({ commit }, payload) {
            setTimeout(() => {
                commit('changeAge', payload);
            }, 1000);
        }
    },

实现原理 触发dispatch 就是去actions队列里面找到对应的actionName的函数 触发传入payload,可以异步操作,操作完毕后就commit数据

 dispatch = (actionName, payload) => {
        this.actions[actionName] && this.actions[actionName].forEach(fn => fn(payload))
    }

整体来说 dispatch commit 都是执行用户定义的函数。
如果有命名空间的模块, commit dispatch 函数名都加上模块名,getters对应属性的取值加上模块名再取 否则不需要加,会触发所以模块的相关名称函数。

7、动态注册模块

 <button @click="registerModule">动态注册模块</button>
export default {
  name: "app",
  methods: {
    registerModule() {
      store.registerModule("b", {
        state: {
          name: "jiangwen",
          age: 30,
        },
        getters:{
          bAge(state){
            return state.age+1;
          }
        }
      });
    },
  },
};

store.registerModule实现动态注册模块

源码实现 vuex/store.js

registerModule(path, module) { // 最终都转换成数组  register(['a','c'])
        if (typeof path == 'string') path = [path];

        // module 是用户直接写的
        this._modules.register(path, module); // 模块的注册, 将用户给的数据放到树中
        // 注册完毕后 ,在进行安装

        // 将用户的module 重新安装
        installModule(this, this.state, path, module.newModule);

        // vuex内部重新注册的话 会重新生成实例, 虽然重新安装了 ,只解决了状态的问题,但是computed就丢失了
        resetVM(this, this.state); // 销毁重来
    }

7.1、根据传参 path类型 如 ‘b’, 转换为path = [‘b’]
7.2、然后调用模块收集器this._modules的register方法, 注册一个子模块b, 这个b模块此时会生成一个格式化后的模块叫module.newModule,
同时这个模块会挂载放在这个根模块下面,为了后面的命名空间取值一致性问题
7.3、安装这个新模块 传入path 和 格式化后的模块叫module.newModule,, 执行installModule,实际就是把wrapperGetters mutations actions 挂载在store上面,用命名空间做区分。
同时也改变了store.state的值
7.4、然后执行 resetVM(this, this.state); 从新传入新的state,创建一个新的vue实例,就能get新的state了,销毁旧的实例。

总的来说,注册模块就是把注册的模块生成格式化后的newModule 然后找到父级module,放在父级module里面,然后去安装,挂载所有 mutations actions等函数,和在父级放改模块state,然后store重新生成一个新的_vm.

8、vuex插件定义和使用

let store = new Vuex.Store({
    // vuex持久化插件?
    plugins: [
        logger(),
        persists() // 每次状态变化都可以存入到localStorage中
    ],
    ...
    });

插件写法 要返回一个函数 方便传参store

function logger() {
    return function(store) {
        let prevState = JSON.stringify(store.state);
        store.subscribe((mutation, state) => {
            // 所有的更新操作都基于mutation (状态变化都是通过mutation的)
            // 如果直接手动的更改状态 此scbscribe是不会执行  commit()
            console.log('prevState:' + prevState);
            console.log('mutation:' + JSON.stringify(mutation));
            console.log('currentState:' + JSON.stringify(state));
            prevState = JSON.stringify(state);
        });
    };
}

new Store时就会触发所有插件函数 传入store

if (options.plugins) { // 说明用户使用了插件
    options.plugins.forEach(plugin => plugin(this))
   }

主要做的就是订阅, store状态改变就会触发订阅函数
store.subscribe订阅处理 传入订阅的函数,里面执行逻辑就行 比如console 新旧的state
在这里插入图片描述
订阅函数触发的时机:commit数据后 就会主动触发订阅函数, 传入 { type: ns + key, payload }, store.state

store.mutations[ns + key].push((payload) => {
    store._withCommittting(() => {
           fn.call(store, getNewState(store, path), payload); // 先调用mutation 在执行subscirbe
    })
    store._subscribes.forEach(fn => fn({ type: ns + key, payload }, store.state));
})

持久化插件 就是缓存state 然后重新进入页面就replaceState

function persists() {
    return function(store) {
        // vuex-persists
        let localState = JSON.parse(localStorage.getItem('VUEX:STATE'));
        if (localState) {
            store.replaceState(localState);
        }

        // 和 mutation挂钩的
        store.subscribe((mutation, rootState) => {
            // 状态变化了 想做一些其他事
            // 状态发生变化就存localStorage中
            // 防抖
            localStorage.setItem('VUEX:STATE', JSON.stringify(rootState));
        });
    };
}

store.js里面定义了replaceState

replaceState(newState) { // 需要替换的状态

        this._withCommittting(() => {
            this._vm._data.$$state = newState; // 替换最新的状态, 赋予对象类型会被重新劫持
        })

        
    }

插件使用就是订阅commit事件,触发订阅函数,没别的了。

9、mapState、mapGetters、mapMutations、mapActions函数原理

使用案例

<!--
 * @description: 
 * @author: steve.deng
 * @Date: 2021-03-29 16:20:29
 * @LastEditors: steve.deng
 * @LastEditTime: 2021-05-06 15:54:37
-->
<template>
    <div id="app">
        学校的年龄{{ name }} 我的年龄是 {{ myAge }}
        <button @click="changeAge(10)">更改年龄</button>
        <button @click="changeAgeAsync(10)">异步年龄</button>
    </div>
   
</template>

<script>
import store from './store';
// import { mapState, mapGetters, mapMutation, mapActions } from "vuex";
import { mapState, mapGetters, mapMutation, mapActions } from '@/vuex';

export default {
    name: 'app',
    methods: {
        registerModule() {
            store.registerModule('b', {
                state: {
                    name: 'jiangwen',
                    age: 30
                },
                getters: {
                    bAge(state) {
                        return state.age + 1;
                    }
                }
            });
        },
        ...mapMutation(['changeAge']),
        // changAge(payload){
        //   this.$store.commit('changeAge', payload)
        // },
        changeAgeAsync(payload) {
            this.$store.dispatch('changeAge', payload);
        }
    },
    computed: {
        ...mapState(['name']), // mapXxxx返回的是对象
        ...mapGetters(['myAge'])
        // name(){
        //   return this.$store.state.name
        // },
        // myAge(){
        //   return this.$store.getters.myAge
        // }
    }
};
</script>

<style></style>

源码逻辑

import install from './install';
import Store from './store'



function mapState(stateList) {
    let obj = {};
    for (let i = 0; i < stateList.length; i++) {
      let stateName = stateList[i];
      obj[stateName] = function () {
        return this.$store.state[stateName];
      };
    }
    return obj;
  }
  function mapGetters(gettersList) {
    let obj = {};
    for (let i = 0; i < gettersList.length; i++) {
      let getterName = gettersList[i];
      obj[getterName] = function () {
        return this.$store.getters[getterName];
      };
    }
    return obj;
  }
  function mapMutations(mutationList) {
    let obj = {};
    for (let i = 0; i < mutationList.length; i++) {
      obj[mutationList[i]] = function (payload) {
        this.$store.commit(mutationList[i], payload);
      };
    }
    return obj;
  }
  function mapActions(actionList) {
    let obj = {};
    for (let i = 0; i < actionList.length; i++) {
      obj[actionList[i]] = function (payload) {
        this.$store.dispatch(actionList[i], payload);
      };
    }
    return obj;
  }
export default {
    install,
    Store,
    mapState,
    mapGetters,
    mapMutations,
    mapActions
}

mapState: 实际就是把传入参数[‘name’],组装一个对象 遍历这个数组参数,对象里面有个name函数,这个函数返回的是this.store.state.name;本质就是this.store.state.name; 本质就是this.store.state.name;this.store.state.name的简写

mapGetters: 同理 返回一个个函数 里面取值逻辑 this.$store.getters.myAge

mapMutations: 同理 ,也是返回一个个函数 里面是commit逻辑 this.$store.commit(mutationList[i], payload);

mapActions: 同理,也是返回一个个函数 里面是dispatch逻辑 this.$store.dispatch(actionList[i], payload);

10、总结

vuex本质就是维护一个store对象,store对象的state其实又是一个vue实例的state,故他们会有双向绑定的特点,把getters的取值放在了vue的computed里面取值,然后用户的commit, dispatch方法,直接放在store对象下面,只不过是按照命名空间,在key上做下文章区分而已,本质都是一个大store包含全部模块的方法。最终要修改state就依靠commit函数去触发用户定义的mutation函数,然后修改state, 集中式修改state,保证安全,插件的话只是用了订阅发布模式,mapMutations等方法就是mutations函数抽象后的处理。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值