store/index.js

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

// 实现内置插件,修改 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);

        })
    }
}

// 实现 vuex-persists 插件(持久化 store)
function persists() {
    return function (store) {
        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));
        });
    }
}

let store = new Vuex.Store({ // vuex持久化插件?
    plugins: [
        logger(),
        persists() // 每次状态变化都可以存入到 localStorage中
    ],
    state: { // state = > data
        name: 'zhangsan',
        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
                            },
                        }
                    }
                }
            }
        },
        b: {
            namespaced: true,
            state: {
                name: 'b'
            },
        }
    }
})

export default store;

store.js

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: 'zhangsan', 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);
        })
    }

    // ......

    // 遍历 mutation 放入 store.mutation
    module.forEachMutation((fn, key) => {
        // {myAge:[fn,fn]},没有命名空间的时候,要合并相同名字的 mutation
        store.mutations[ns + key] = store.mutations[ns + key] || [];
        store.mutations[ns + key].push((payload) => {
            store._withCommittting(() => {
                // 防止 this 丢失
                fn.call(store, getNewState(store, path), payload); // 先调用 mutation 再执行 subscirbe
            })

            // 插件的更新依赖于 mutations 的执行
            store._subscribes.forEach(fn => fn({ type: ns + key, payload }, store.state));
        })
    });

    // ......

    // 递归安装
    module.forEachChildren((child, key) => {
        installModule(store, rootState, path.concat(key), child);
    });
}

function resetVM(store, state) {
    let oldVm = store._vm;

    store.getters = {}; // 初始化 store.getters
    const computed = {}; // 取 getters 属性时,把他代理到 vue 实例的计算属性 computed 上
    forEach(store.wrapperGetters, (getter, key) => {
        computed[key] = getter;

        // 给 store.getters 赋值
        Object.defineProperty(store.getters, key, {
            // 在 vue 组件中使用 vuex 的 getters 时,代理到 vue 实例 store._vm 上
            get: () => store._vm[key]
        })
    });

    // 需要将 state 变成响应式的,直接将 state 放到 vue 实例的 data 里面就可以了,这样 state 就是响应式的,会自动更新视图
    store._vm = new Vue({
        data: {
            // 以 $ 开头的数据不会被挂载到实例上,但是会挂载到当前的 _data 上,减少一次代理
            // $$ 开头,表示是 vuex 自己自定义的变量
            $$state: state
        },
        computed,
    });

    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 修改的属性,默认不是在 mutation 中更改的

        this.strict = options.strict;

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

        // 将包装后的模块进行安装
        installModule(this, state, [], this._modules.root);

        // getters 计算属性的实现
        resetVM(this, state);

        // 说明用户使用了插件
        if (options.plugins) {
            options.plugins.forEach(plugin => plugin(this))
        }
    }
    subscribe(fn) {
        this._subscribes.push(fn);
    }
    replaceState(newState) { // 需要替换的状态
        this._vm._data.$$state = newState; // 替换最新的状态, 赋予对象类型会被重新劫持
        // 虽然替换了状态,但是 mutation getter 中的 state 在初始化的时候,已经被绑定死了老的状态
        // 所以在获取 state 的地方要改成 => 获取最新的状态(resetVM 方法)
    }
}
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

中午好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. store/index.js
  2. 2. store.js