Vuex


vuex 借助了vue的响应式,因此只能在vue中使用

$store属性挂载

Vue.use(vuex), 会给每个组件挂载上$store属性,便于store取值和方法的调用

  • 利用全局的mixin中的beforeCreate声明周期,在每个组件挂载实例前,先挂载$store属性
  • Vue渲染页面时,先渲染父组件,遇到子组件才会渲染子组件
  • 如果new Vue 传入了store属性,说明是需要给子组件挂载$store属性,如果没有,则不会挂载
  • 判断是否有options.store , 如果有,先给根实例挂载$store, 然后渲染子组件时,在将父组件的store挂载到当前组件的实例上

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

简单实现

借助Vue响应式和依赖收集的特性,简单实现

  • state: 将state挂载到data中,当访问state属性时,代理到data中的state,进行依赖收集,修改state,通知更新(如果data中的属性以$开头,将不会把此属性代理到当前vue实例上,只能通过vm._data访问,因为我们的state建议只通过mutation更改,所以尽量少一层代理,减少用户直接更改的可能性)
  • getters: 借助了Vue组件中的computed选项,遍历所有的getters,重写方法,将state传入,然后挂载Vue options中的computed属性上,每次访问getters的属性,代理到computed中,实现计算属性的作用(缓存,根据state计算)
  • mutations: 遍历对象中的方法,将方法重写,改变this,传入state和payload
  • actions: 遍历对象中的方法,将方法重写,this改成当前store,传入store和payload

class Store { // new Vue.Store 缠身一个实例
    constructor(options) {
        // 以下这些变量都是用户传递的
        let { state, getters, mutations, actions, module, strict } = options;
        this.getters = {}; // 我再取getters属性的时候 把他代理到计算属性上
        const computed = {};
        forEach(getters, (fn, key) => {
            computed[key] = () => {
                return fn(this.state); // 为了保证参数是state
            }
            // 当我们去getters上取值 需要对computed取值
            Object.defineProperty(this.getters, key, {
                get: () => this._vm[key] // 具备了缓存的功能
            })
        });
        // ----------
        this.mutations = {};
        forEach(mutations, (fn, key) => {
            this.mutations[key] = (payload) => fn.call(this, this.state, payload);
        });

        // ------dispatch中派发的是动作,里面可以有异步逻辑,更改状态都要通过mutation,mutation是同步更改的-------
        this.actions = {}
        forEach(actions, (fn, key) => {
            this.actions[key] = (payload) => fn.call(this, this, payload);
        });
        // 这个状态在页面渲染时需要收集对应的渲染watcher,这样状态更新才会更新视图
        this._vm = new Vue({
            data: { // $符号开头的数据不会被挂载到实例上,但是会挂载到当前的_data上,减少了一次代理
                $$state: state // 状态在哪里取值,就会收集对应的依赖
            },
            computed
        });
        // 用户组件中使用的$store = this
    }
    // 类的属性访问器
    get state() { // this.$store.state => defineProperty中的get
        // 依赖于 vue的响应式原理
        return this._vm._data.$$state
    }
    dispatch = (type, payload) => { // 根据commit还是dispatch 找对应的存储结果
        this.actions[type](payload)
    }
    commit = (type, payload) => {
        this.mutations[type](payload)
    }
}

modules 模块

  • 将传入的options格式化成父子结构(注册)
  • 给带有namespaced = true选项的选项单独存储到一个对象中
  • 定义成响应式
  • 如果有插件,遍历传入的方法并执行,将Store实例传入
  • 严格模式: 只能内置方法修改state或者用户通过mutations修改state,否则报错(_withCommittting,利用高阶函数,修改state前将_committing表示修改为true,执行完成修改成false)


import { Vue } from './install'
import ModuleCollection from './module/module-collection';
import { forEach } from './util';

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

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);
    });
}

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())
    }
}
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); // 销毁重来
    }
}
// state getters action mutation  (modules 分层)
export default Store;

动态注册mudules

store.registerModule


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); // 销毁重来
    }

map方法



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;
  }

vuex plugins

插件实现原理:

  • new Store会ch查看是否有plugins选项,如果有,遍历执行所有插件并传入store
  • 在编写plugins可以通过store.subscribe(fn)
  • 在每次mutations执行完毕后会遍历_subscribes执行内部订阅的所有方法
  • 每个fn内部回把store,修改后的state,payload传入fn中并且执行

// 编写plugin
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);

        })
    }
}
// 调用plugin
if (options.plugins) { // 说明用户使用了插件
    options.plugins.forEach(plugin => plugin(this))
}
// 执行mutations后执行执行订阅方法(发布)
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));
})

文章作者: Jia
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Jia !
  目录