vuex自定义实现

# 简介

实现简版vuex

# 基本用法【SGMA】

vuex是vue的状态管理工具,为了更方便实现多个组件共享状态;

这里我们可以进行类比: 5个核心SGMA + 1个插件P

  • state 类比为组件的状态 ;
  • getters类比为组件的计算属性 ;
  • mutations类比为组件中的方法(可以更改组件的状态),使用commit;
  • actions用于进行异步操作将结果提交给mutation; 使用dispatch;

img

import Vuex from 'vuex'
Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    age: 18
  },
  getters: {
    getAge(state) {
      return state.age + 10;
    }
  },
  mutations: {
    changeAge(state, payload) {
      state.age += payload
    }
  },
  actions: {
    changeAge({
      commit
    }, payload) {
      setTimeout(() => {
        commit('changeAge', payload);
      }, 1000);
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<div id="app">
    我的年龄是: {{this.$store.state.age}}
    <br />
    年龄是: {{this.$store.getters.getAge}}
    <br />
    <!-- commit 对应的mutation -->
    <button @click="$store.commit('changeAge',5)">立即增加年龄
    <!-- dispatch对应的action -->
    <button @click="$store.dispatch('changeAge',3)">过一会增加年龄</button>
   </button>
</div>
1
2
3
4
5
6
7
8
9
10
11

这个$store属性是通过根实例传入的

new Vue({
  store,
  render: h => h(App)
}).$mount('#app')
1
2
3
4

内部会将store属性挂载在每个实例上命名为$store,这样所有组件都可以操作同一个store属性

# 简单版本

实现入口文件,默认导出Store类和install方法

import { Store, install } from './store';
export default {
    Store,
    install
}
export {
    Store,
    install
}
1
2
3
4
5
6
7
8
9

# 1.install方法

import applyMixin from './mixin'
let Vue;
export class Store {
    constructor(options){}
}
export const install = (_Vue) =>{
    Vue = _Vue; // 保留vue的构造函数
    applyMixin(Vue);
}
export default {
  Store,
  install,
};
1
2
3
4
5
6
7
8
9
10
11
12
13

当我们使用插件时默认会执行install方法并传入Vue的构造函数

# 2.mixin方法

将store实例定义在所有的组件实例上;

const applyMixin = (Vue) => {
    Vue.mixin({
        beforeCreate: vuexInit
    })
}
// 需要把根组件中store实例给每个组件都增加一个$store的属性
function vuexInit() {
    const options = this.$options;
    if (options.store) { // 是否是根组件
        this.$store = options.store;// 给根实例增加$store属性
    } else if (options.parent && options.parent.$store) {
        // 子组件 深度优先 父-> 子 -> 孙子
        this.$store = options.parent.$store;// 给组件增加$store属性
    }
}
export default applyMixin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 3.实现state

vuex核心就是借用了vue的实例 因为vue的实例数据变化 会刷新视图;

什么样的属性可以实现双向 ; 有get 和set new vue({data:{}})

export class Store {
    constructor(options){
        let state = options.state;
        this._vm = new Vue({
            data:{
                $$state:state,//这个数据会放入到_data下
            }
        });
    }
    get state(){ //state别名化简化;注意这里get类的使用
        return this._vm._data.$$state; //优化缩写
    }
}
function forEachValue(obj, callback) {
  //Object.keys(obj).forEach((item) => callback(item, obj[item]));
  Object.keys(obj).forEach((item) => callback(obj[item],item));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

将用户传入的数据定义在vue的实例上 (这个就是vuex核心)产生一个单独的vue实例进行通信,这里要注意的是定义$开头的变量不会被代理到实例上

# 4.实现getters

通过vue中的computed实现;注意下面的先后顺序;

this.getters = {};
const computed = {}
forEachValue(options.getters, (fn, key) => {
    computed[key] = () => {
        return fn(this.state);//设置时是函数,使用时直接就是属性;
    }
    Object.defineProperty(this.getters,key,{
        get:()=> this._vm[key] //使用时才调用;
    })
});// 注意顺序
this._vm = new Vue({//借助vue的实现动态属性;
    data: {
        $$state: state, // $开头的,不会暴露;用$$state源码也是这样定义的;
    },
    computed // 利用计算属性实现缓存
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 5.实现mutations

暴露commit方法;注意call传参不同;fn.call(this, this.state, payload)

export class Store {
    constructor(options) {
        this.mutations = {};
        forEachValue(options.mutations, (fn, key) => {
            this.mutations[key] = (payload) => fn.call(this, this.state, payload)
        });
    }
    commit = (type, payload) => { //利用ES6不改变this指向;commit有两种使用方式
        this.mutations[type](payload);
    }
}
1
2
3
4
5
6
7
8
9
10
11

# 6.实现actions

暴露dispatch方法;注意call传参不同;fn.call(this, this, payload)

export class Store {
    constructor(options) {
        this.actions = {};
        forEachValue(options.actions, (fn, key) => {
            this.actions[key] = (payload) => fn.call(this, this, payload);
        });
    }
    dispatch = (type, payload) => {
        this.actions[type](payload);
    }
}
1
2
3
4
5
6
7
8
9
10
11

image-20201116151817406

# 模块机制

**核心方法:**三步骤;

  • 模块收集;数据的格式化 格式化成我们想要的结果 (树);this._modules = new ModuleCollection(options);【树状化】
  • 安装模块;根模块的状态中 要将子模块通过模块名定义在根模块上;【展平树结构】installModule(this, state, [], this._modules.root);
  • 重新设置;将状态和getters都定义在当前的vm上;resetStoreVM(this, state);

# 1.格式化用户数据

基本中的使用

import a from "./a.js";
import b from "./b.js";
const store = new Vuex.Store({
  modules: {// 模块;注意命名空间
    a,
    b,
  },
}
//a.js a---》c.js
import c from "./c";
export default {
  namespaced: true,
  state: {
    age: 222,
  },
  getters: {},
  actions: {},
  mutations: {
    changeAge(state, payload) {
      state.age += payload;
    },
  },
  modules: {
    c,
  },
};
//b.js                   
export default {
  namespaced: true,
  state: {
    age: 333,
  },
  getters: {},
  actions: {},
  mutations: {
    changeAge(state, payload) {
      state.age += payload;
    },
  },
};
//a下面的 c.js 
export default {
  state: {
    age: 444,
  },
  getters: {},
  actions: {},
  mutations: {
    changeAge(state, payload) {
      state.age += payload;
    },
  },
};                   
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

实现:多次层次结构变成树状结构

console.log("--register----newModule-----", newModule);
      { state: {age: 10}
      _children: {}
      namespaced: false
      _raw: {state: {}, getters: {}, mutations: {}, actions: {}, modules: {},}
      }

//最后的模块树结构
/**
 * this.root = {
 *    _raw: '根模块',
 *    _children:{
 *        a:{
 *          _raw:"a模块",
 *          _children:{
 *              c:{
 *                  .....
 *              }
 *          },
 *          state:'a的状态'
 *        },
 *        b:{
 *          _raw:"b模块",
 *          _children:{},
 *          state:'b的状态'
 *        }
 *    },
 *    state:'根模块自己的状态'
 * }
 */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import ModuleCollection from './module/module-collection'
this._modules = new ModuleCollection(options);
1
2
import { forEachValue } from '../util'
export default class ModuleCollection {
    constructor(options) {
        this.register([], options)
    }
    register(path, rootModule) {
        let newModule = {
            _raw: rootModule,
            _children: {},
            state: rootModule.state
        };
        if (path.length == 0) {
            this.root = newModule;
        } else {
             //获取父级module;除去后面一个自己遍历数组;
            let parent = path.slice(0,-1).reduce((memo,current)=>{
                return memo._children[current];
            },this.root);
            parent._children[path[path.length-1]] = newModule;
        }
        if (rootModule.modules) {
            forEachValue(rootModule.modules, (module, moduleName) => {
                // [a] [a,c] [b]
                this.register(path.concat(moduleName), module);
            })
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 抽离模块类

方便后续我们对模块扩展;对方便封装,容易维护;

export default class Module {
  constructor(newModule) {// 每个模块的基本元素结构字段
    this._raw = newModule;
    this._children = {};
    this.state = newModule.state;
  }
  get namespaced() {
    return !!this._raw.namespaced;
  }
  getChild(key) {
    return this._children[key];
  }
  addChild(key, module) {
    this._children[key] = module;
  }
  // 给模块继续扩展方法
 // 有mutations就遍历这个mutation
  forEachMutation(fn) {
    if (this._raw.mutations) {
      forEachValue(this._raw.mutations, fn);
    }
  }
  // 在模块类中提供遍历方法
  forEachAction(fn) {
    if (this._raw.actions) {
      forEachValue(this._raw.actions, fn);
    }
  }
  forEachGetter(fn) {
    if (this._raw.getters) {
      forEachValue(this._raw.getters, fn);
    }
  }
  forEachChild(fn) {
    forEachValue(this._children, fn);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

循环注册,生产模块树结构;

class ModuleCollection {
  constructor(options) {
    this.register([], options);// 第一次,默认从根里面加载
  }

  register(path, rootModule) {
    let newModule = new Module(rootModule); // [a, c]   [b]
    if (path.length == 0) {
      this.root = newModule; // 根模块; this.root就是树根
    } else {
      let parent = path.slice(0, -1).reduce((memo, current) => {
        return memo.getChild(current);
      }, this.root);
      parent.addChild(path[path.length - 1], newModule);
    }
    if (rootModule.modules) {// 循环模块 [a] [b]
      forEachValue(rootModule.modules, (module, moduleName) => {
        this.register(path.concat(moduleName), module); // [a,c]
      });
    }
  }
  
  //实现命名空间
  getNamespaced(path) {
    let root = this.root; // 从根模块找起来
    return path.reduce((str, key) => {
      root = root.getChild(key); // 不停的去找当前的模块
      return str + (root.namespaced ? key + "/" : "");// [a,c] ==> a/c
    }, ""); // 参数就是一个字符串
  }
}

//最后的模块树结构
/**
 * this.root = {
 *    _raw: '根模块',
 *    _children:{
 *        a:{
 *          _raw:"a模块",
 *          _children:{
 *              c:{
 *                  .....
 *              }
 *          },
 *          state:'a的状态'
 *        },
 *        b:{
 *          _raw:"b模块",
 *          _children:{},
 *          state:'b的状态'
 *        }
 *    },
 *    state:'根模块自己的状态'
 * }
 */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

store.js中调用

// 1.模块收集; // 数据的格式化 格式化成我想要的结果 (树)
this._modules = new ModuleCollection(options);
1
2

# 2.安装模块

安装模块;根模块的状态中 要将子模块通过模块名定义在根模块上

核心:Vue.set(parent, path[path.length - 1], module.state);

this._actions = {};
this._mutations = {};
this._wrappedGetters = {}
installModule(this, state, [], this._modules.root);
1
2
3
4

在模块类中提供遍历方法;

export default class Module {
  constructor(newModule) {
    // 每个模块的基本元素结构字段
    this._raw = newModule;
    this._children = {};
    this.state = newModule.state;
  }
  get namespaced() {
    return !!this._raw.namespaced; //判断是否有命名空间
  }
  getChild(key) {
    return this._children[key];
  }
  addChild(key, module) {
    this._children[key] = module;
  }
  // 给模块继续扩展方法
  forEachMutation(fn) {
    if (this._raw.mutations) {
      forEachValue(this._raw.mutations, fn);
    }
  }
  // 在模块类中提供遍历方法
  forEachAction(fn) {
    if (this._raw.actions) {
      forEachValue(this._raw.actions, fn);
    }
  }
  forEachGetter(fn) {
    if (this._raw.getters) {
      forEachValue(this._raw.getters, fn);
    }
  }
  forEachChild(fn) {
    forEachValue(this._children, fn);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

对模块进行安装操作;从raw里面的原始数据挂载到store.xxx中;

function installModule(store, rootState, path, module) {
    if (path.length > 0) {
        let parent = path.slice(0, -1).reduce((memo, current) => {
            return memo[current];
        }, rootState);
        // 如果这个对象本身不是响应式的;那么Vue.set也会跟对象添加熟悉;就相当于obj[属性]=值
      // parentState[path[path.length - 1]] = module.state; //要将子模块定义在父模块之上
        //响应式添加数据;新增不存在的属性需要使用set方法
        Vue.set(parent, path[path.length - 1], module.state);
    }
    
    module.forEachMutation((mutation, key) => {
        store._mutations[key] = (store._mutations[key] || []);
        store._mutations[key].push((payload) => {
            mutation.call(store, module.state, payload);
        });
    });
    module.forEachAction((action, key) => {
        store._actions[key] = (store._actions[key] || []);
        store._actions[key].push(function(payload) {
            action.call(store, this, payload);
        });
    });
    module.forEachGetter((getter, key) => {
        //注意这里是_wrappedGetters;在resetStoreVM中用到;
        store._wrappedGetters[key] = function() {
            return getter(module.state);
        }
    });
    // 递归加载模块
    module.forEachChild((child, key) => {
        installModule(store, rootState, path.concat(key), child)
    })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

dispatchaction方法进行重写

commit = (type, payload) => {
    this._mutations[type].forEach(fn => fn.call(this, payload));
}
dispatch = (type, payload) => {
    this._actions[type].forEach(fn => fn.call(this, payload));
}
1
2
3
4
5
6

# 3.重置状态和计算属性

# resetStoreVM

将状态和getters都定义在当前的vm上;注意先后顺序

resetStoreVM(this, state);
1
function resetStoreVM(store, state) {
    const computed = {};
    store.getters = {};
    const wrappedGetters = store._wrappedGetters
    forEachValue(wrappedGetters, (fn, key) => {
        computed[key] = () => {
            return fn(store.state);
        }
        Object.defineProperty(store.getters, key, {
            get: () => store._vm[key]
        })
    });
    store._vm = new Vue({
        data: {
            $$state: state,
        },
        computed
    });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 实现命名空间

import { forEachValue } from '../util';
import Module from './module';
export default class ModuleCollection {
  /**
   * 实现命名空间
   * [a,c] ==> a/c
   * ['a','c','e'] ==> a/c/e/
   */
  getNamespaced(path) {
    let root = this.root; // 从根模块找起来
    return path.reduce((str, key) => {
      root = root.getChild(key); // 不停的去找当前的模块
      return str + (root.namespaced ? key + "/" : "");
    }, ""); // 参数就是一个字符串
  }
}
export default class Module {
    get namespaced(){
        return !!this._rawModule.namespaced;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

在绑定属性是增加命名空间即可

/**
 * 遍历当前模块上的 actions、mutation、getters事件都把它定义在父上面
 * @param {*} store  容器
 * @param {*} rootState 根模块
 * @param {*} path  所有路径
 * @param {*} module 刚刚格式化后的结果
 */
const installModule = (store, rootState, path, module) => {
  // 给当前订阅的事件 增加一个命名空间
  let namespace = store._modules.getNamespaced(path); // 返回前缀即可;在绑定属性是增加命名空间即可
  // a/changeAge  b/changeAge   a/c/changeAge   path[a] [b] [a,c]
  if (path.length > 0) {
    //通过路径找到parent;  vuex可以动态的添加模块;
    let parent = path.slice(0, -1).reduce((memo, current) => {
      return memo[current];
    }, rootState);
    // 如果这个对象本身不是响应式的;那么Vue.set也会跟对象添加熟悉;就相当于obj[属性]=值
    Vue.set(parent, path[path.length - 1], module.state);
  }
  // 获取最新状态
  module.forEachMutation((mutation, key) => {
    const namespaceKey = namespace + key;
    store._mutations[namespaceKey] = store._mutations[namespaceKey] || [];
    store._mutations[namespaceKey].push((payload) => {
      mutation.call(store, getState(store, path), payload); // 用最新的状态
      store._subscribes.forEach((fn) => {
        fn(mutation, store.state);
      });
    });
  });
  module.forEachAction((action, key) => {
    const namespaceKey = namespace + key;
    store._actions[namespaceKey] = store._actions[namespaceKey] || [];
    store._actions[namespaceKey].push((payload) => {
      action.call(store, store, payload);
    });
  });
  module.forEachGetter((getter, key) => {
    // 模块中getter的名字重复了会覆盖;所以要注意命名空间
    const namespaceKey = namespace + key;
    store._wrappedGetters[namespaceKey] = function() {
      return getter(getState(store, path)); // 用最新的状态
    };
  });
  // 递归加载模块
  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child);
  });
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

# 4.注册模块

注册一个动态module,当业务进行异步加载的时候,可以通过该接口进行注册动态module

registerModule(path,rawModule){
    if(typeof path == 'string') path = [path];
    this._modules.register(path, rawModule);
    installModule(this, this.state, path, rawModule.rawModule);
    // 重新设置state, 更新getters
    resetStoreVM(this,this.state);
} 
1
2
3
4
5
6
7

实现模块的注册,就是将当前模块注册到_modules中

// persist存储后更新数据有用到;
function resetStoreVM(store, state) {
+   let oldVm = store._vm;
    const computed = {};
    store.getters = {};
    const wrappedGetters = store._wrappedGetters
    forEachValue(wrappedGetters, (fn, key) => {
        computed[key] = () => {
            return fn(store.state);
        }
        Object.defineProperty(store.getters, key, {
            get: () => store._vm[key]
        })
    });
    store._vm = new Vue({
        data: {
            $$state: state,
        },
        computed
    });
    if(oldVm){
+      Vue.nextTick(() => oldVm.$destroy())// 销毁上次创建的实例
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 插件机制

涉及到plugin, subcribe, replaceState的方法定义;

**核心方法:**三步骤;

  • plugins;
  • subcribe;
  • replaceState;

# 1.实现loggervue-persist插件使用方式

// import logger from "vuex/dist/logger";
// import VuexPersistence from "vuex-persist";

/**
 * plugin要实现的方法有;
 * plugins subscribe replaceState
 */
// 自己实现vuex-persist插件
class VuexPersistence {
  constructor({ storage }) {
    this.storage = storage;
    this.localName = "VUEX-MY";
  }
  plugin = (store) => {
    let localState = JSON.parse(this.storage.getItem(this.localName));
    if (localState) store.replaceState(localState);
    store.subscribe((mutationName, state) => {
      // 这里需要优化;做一个节流  throttle lodash
      this.storage.setItem(this.localName, JSON.stringify(state));
    });
  };
}

// 自己实现logger插件
const logger = () => (store) => {
  let prevState = JSON.stringify(store.state);
  store.subscribe((mutationName, state) => {
    // 监听变化的 ,每次数据变化都会执行此方法 commit 的时候会执行此方法
    console.log("prev" + prevState);
    console.log(mutationName);
    prevState = JSON.stringify(state);
    console.log("next", prevState);
  });
};

const vuexLocal = new VuexPersistence({
  storage: window.localStorage,
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 2.实现plugin,subscribe,replaceState方法

# 发布订阅模式

// 初始化时,执行插件
  plugins: [
    // 插件从上到下执行
    vuexLocal.plugin, // 放在这里 插件默认就会显执行一次
    logger(),
  ],
//options.plugins.forEach(plugin => plugin(this));
subscribe(fn){
	this._subscribers.push(fn);
}
//还有是在提交commit后执行
 this._subscribes.forEach((fn) => fn({ type, payload }, this.state));
1
2
3
4
5
6
7
8
9
10
11
12
replaceState(state){
	this._vm._data.$$state = state; //直接修改,记得配合下面的replace替换新的数据
}
1
2
3

# 3.获取最新状态replaceState

外部通过replaceState更新了state, 故要定义获取最新的state方法;

function getState(store, path) {
    let local = path.reduce((newState, current) => {
        return newState[current]; 
    }, store.state);
    return local
}

replaceState(newState) {
    this._withCommiting(() => {
      this._vm._data.$$state = newState; // 替换掉最新的状态
      //可直接提交;但是调用mutation时传入最新状态;
    });
}

module.forEachMutation((mutation, key) => {
    store._mutations[namespace + key] = (store._mutations[namespace + key] || []);
    store._mutations[namespace + key].push((payload) => {
    mutation.call(store, getState(store,path), payload);
    });
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

调用mutation时传入最新状态

# 辅助函数

其实mapX就是简化this.$store.X的操作流程 ; vue3后面没有这辅助函数一块;

比如:age(){return this.$store.state.age}

# 1.mapState实现

const mapState = arrList => {
  let obj = {};
  for (let i = 0; i < arrList.length; i++) {
    let stateName = arrList[i];
     //注意这里是高阶函数;
    obj[stateName] = function() {
      return this.$store.state[stateName];
    };
  }
  return obj;
};
1
2
3
4
5
6
7
8
9
10
11

# 2.mapGetters实现

const mapGetters = arrList => {
  let obj = {};
  for (let i = 0; i < arrList.length; i++) {
    let getterName = arrList[i]
    obj[getterName] = function() {
      return this.$store.getters[getterName];
    };  
  }
  return obj;
};
1
2
3
4
5
6
7
8
9
10

# 3.mapMutations实现

const mapMutations = mutationList=>{
    let obj = {};
    for (let i = 0; i < mutationList.length; i++) {
        let type = mutationList[i]
        obj[type] = function(payload){
            this.$store.commit(type,payload);
        }
    }
    return obj
}
1
2
3
4
5
6
7
8
9
10

# 4.mapActions实现

const mapActions = actionList=>{
    let obj = {};
    for (let i = 0; i < actionList.length; i++) {
        let type = actionList[i]
        obj[type] = function(payload){
            this.$store.dispatch(type,payload);
        }
    }
    return obj
}
1
2
3
4
5
6
7
8
9
10

# 引入方式及示范

export * from './helpers';

import {createNamespacedHelpers} from 'vuex';
//import {mapState,mapGetters, mapMutations, mapActions} from 'vuex';
let {mapState as mapState1 ,mapGetters} = createNamespacedHelpers('samy');
export default {
    computed: {
        // 直接取出状态中的结果
        ...mapState(['lesson']),
        // 给状态起名字
        ...mapState({lesson1:'lesson'}),
        // 通过函数的方式获取结果
        ...mapState({lesson2:state=>state.lesson})
        
         // getName(){
        //     return this.$store.getters.getLessonName
        // }
        ...mapGetters(['getLessonName'])//通过辅助函数可以简写很多;
    },
    methods: {
        //this.$store.dispatch('SET_LESSON_NAME');
       ...mapMutations(['SET_LESSON_NAME']), //mapMutations简化方法;
       ...mapActions(['Login', 'Logout']),
       changeName(){
          this['SET_LESSON_NAME']({number:10});
       },
       stepCaptchaCancel () {
        this.Logout().then(() => {
        this.loginBtn = false
        this.stepCaptchaVisible = false
      })
    },
   }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 区分mutation和action

# 原理上

核心:_vm.$watch()

this._committing = false;
 _withCommitting(fn) {
    let committing = this._committing;
    this._committing = true; // 在函数调用前 表示_committing为true
    fn();
    this._committing = committing;
}
1
2
3
4
5
6
7
if (store.strict) {
    // 只要状态一变化会立即执行,在状态变化后同步执行
    store._vm.$watch(() => store._vm._data.$$state, () => {
        console.assert(store._committing, '在mutation之外更改了状态')
    }, { deep: true, sync: true });
}


/* 使能严格模式 */
function enableStrictMode(store) {
  store._vm.$watch(
    function () {
      return this._data.$$state;
    },
    () => {
      if (process.env.NODE_ENV !== "production") {
        /* 检测store中的_committing的值,如果是true代表不是通过mutation的方法修改的 */
        assert(
          store._committing,
          `Do not mutate vuex store state outside mutation handlers.`
        );
      }
    },
    { deep: true, sync: true }
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

严格模式下增加同步watcher,监控状态变化

以下三个常规的操作添加监控判断,是否是异步操作,高价函数曝光;

store._withCommitting(() => {
    mutation.call(store, getState(store, path), payload); // 更改状态
})
1
2
3

只有通过mutation更改状态,断言才能通过

replaceState(newState) { // 用最新的状态替换掉
    this._withCommitting(() => {
        this._vm._data.$$state = newState;
    })
}
1
2
3
4
5
store._withCommitting(() => {
    Vue.set(parent, path[path.length - 1], module.state);
})
1
2
3

内部更改状态属于正常更新,所以也需要用_withCommitting进行包裹

# 细分总括

从下面三个方面说;

  • action 提交的是 mutation,而不是直接变更状态。mutation可以直接变更状态;action 可以包含任意异步操作。mutation只能是同步操作
  • 提交方式不同;
    • action 是用this.store.dispatch('ACTION_NAME',data)来提交;
    • mutation是用this.$store.commit('SET_NUMBER',10)来提交;
  • 接收参数不同,mutation第一个参数是state,而action第一个参数是context, [store自己],其包含了

# 自己实现plugin插件

实现VuexPersistencelogger

# 实现VuexPersistence

# 实现logger

ps:这里用到了高阶函数;

class VuexPersistence {
  constructor({ storage }) {
    this.storage = storage;
    this.localName = "VUEX-MY";
  }
  plugin = (store) => {
    let localState = JSON.parse(this.storage.getItem(this.localName));
    if (localState) store.replaceState(localState);
    store.subscribe((mutationName, state) => {
      // 这里需要做一个节流  throttle lodash
      this.storage.setItem(this.localName, JSON.stringify(state));
    });
  };
}
// 自己实现logger插件
const logger = () => (store) => {
  let prevState = JSON.stringify(store.state);
  store.subscribe((mutationName, state) => {
    // 监听变化的 ,每次数据变化都会执行此方法 commit 的时候会执行此方法
    console.log("prev" + prevState);
    console.log(mutationName);
    prevState = JSON.stringify(state);
    console.log("next", prevState);
  });
};

const vuexLocal = new VuexPersistence({
  storage: window.localStorage,
});

Vue.use(Vuex); // 使用这个插件  内部会调用Vuex中的install方法
const store = new Vuex.Store({
  // strict: true,
  plugins: [
    // 插件从上到下执行
    vuexLocal.plugin, // 放在这里 插件默认就会显执行一次
    logger(),
  ],
 })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 相关链接

Vuex 是什么? | Vuex (vuejs.org) (opens new window)

上次更新: 2022/04/15, 05:41:28
×