原文地址 www.zhufengpeixun.com

一.Vuex基本使用及用法

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

  1. import Vuex from 'vuex'
  2. Vue.use(Vuex)
  3. export default new Vuex.Store({
  4. state: {
  5. age: 28
  6. },
  7. getters: {
  8. getAge(state) {
  9. return state.age + 10;
  10. }
  11. },
  12. mutations: {
  13. changeAge(state, payload) {
  14. state.age += payload
  15. }
  16. },
  17. actions: {
  18. changeAge({
  19. commit
  20. }, payload) {
  21. setTimeout(() => {
  22. commit('changeAge', payload);
  23. }, 1000);
  24. }
  25. }
  26. })

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

  1. <div id="app">
  2. 我的年龄是: {{this.$store.state.age}}
  3. <br />
  4. 珠峰的年龄是: {{this.$store.getters.getAge}}
  5. <br />
  6. <!-- dispatch对应的action -->
  7. <button @click="$store.dispatch('changeAge',3)">过一会增加年龄</button>
  8. <!-- commit 对应的mutation -->
  9. <button @click="$store.commit('changeAge',5)">立即增加年龄</button>
  10. </div>

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

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

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

二.自己实现Vuex模块

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

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

1.install方法

  1. import applyMixin from './mixin'
  2. let Vue;
  3. export class Store {
  4. constructor(options){}
  5. }
  6. export const install = (_Vue) =>{
  7. Vue = _Vue;
  8. applyMixin(Vue);
  9. }

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

2.mixin方法

  1. const applyMixin = (Vue) => {
  2. Vue.mixin({
  3. beforeCreate: vuexInit
  4. })
  5. }
  6. function vuexInit() {
  7. const options = this.$options;
  8. if (options.store) {
  9. // 给根实例增加$store属性
  10. this.$store = options.store;
  11. } else if (options.parent && options.parent.$store) {
  12. // 给组件增加$store属性
  13. this.$store = options.parent.$store;
  14. }
  15. }
  16. export default applyMixin

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

3.实现state

  1. export class Store {
  2. constructor(options){
  3. let state = options.state;
  4. this._vm = new Vue({
  5. data:{
  6. $$state:state,
  7. }
  8. });
  9. }
  10. get state(){
  11. return this._vm._data.$$state
  12. }
  13. }

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

4.实现getters

  1. this.getters = {};
  2. const computed = {}
  3. forEachValue(options.getters, (fn, key) => {
  4. computed[key] = () => {
  5. return fn(this.state);
  6. }
  7. Object.defineProperty(this.getters,key,{
  8. get:()=> this._vm[key]
  9. })
  10. });
  11. this._vm = new Vue({
  12. data: {
  13. $$state: state,
  14. },
  15. computed // 利用计算属性实现缓存
  16. });

5.实现mutations

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

6.实现actions

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

三.实现模块机制

1.格式化用户数据

  1. import ModuleCollection from './module/module-collection'
  2. this._modules = new ModuleCollection(options);
  1. import { forEachValue } from '../util'
  2. export default class ModuleCollection {
  3. constructor(options) {
  4. this.register([], options)
  5. }
  6. register(path, rootModule) {
  7. let newModule = {
  8. _raw: rootModule,
  9. _children: {},
  10. state: rootModule.state
  11. };
  12. if (path.length == 0) {
  13. this.root = newModule;
  14. } else {
  15. let parent = path.slice(0,-1).reduce((memo,current)=>{
  16. return memo._children[current];
  17. },this.root);
  18. parent._children[path[path.length-1]] = newModule;
  19. }
  20. if (rootModule.modules) {
  21. forEachValue(rootModule.modules, (module, moduleName) => {
  22. this.register(path.concat(moduleName), module);
  23. })
  24. }
  25. }
  26. }

2.抽离模块类

  1. export default class Module{
  2. constructor(rawModule){
  3. this._children = {};
  4. this._rawModule = rawModule;
  5. this.state = rawModule.state
  6. }
  7. getChild(key){
  8. return this._children[key]
  9. }
  10. addChild(key,module){
  11. this._children[key] = module
  12. }
  13. }

后续我们对模块扩展会更加的方便,哈哈~


3.安装模块

  1. this._actions = {};
  2. this._mutations = {};
  3. this._wrappedGetters = {}
  4. // 安装模块
  5. installModule(this, state, [], this._modules.root);

在模块类中提供遍历方法

  1. export default class Module {
  2. constructor(rawModule) {
  3. this._children = {};
  4. this._rawModule = rawModule;
  5. this.state = rawModule.state
  6. }
  7. getChild(key) {
  8. return this._children[key]
  9. }
  10. addChild(key, module) {
  11. this._children[key] = module
  12. }
  13. forEachMutation(fn) {
  14. if (this._rawModule.mutations) {
  15. forEachValue(this._rawModule.mutations, fn)
  16. }
  17. }
  18. forEachAction(fn) {
  19. if (this._rawModule.actions) {
  20. forEachValue(this._rawModule.actions, fn)
  21. }
  22. }
  23. forEachGetter(fn) {
  24. if (this._rawModule.getters) {
  25. forEachValue(this._rawModule.getters, fn)
  26. }
  27. }
  28. forEachChild(fn) {
  29. forEachValue(this._children, fn);
  30. }
  31. }

对模块进行安装操作

  1. function installModule(store, rootState, path, module) {
  2. if (path.length > 0) {
  3. let parent = path.slice(0, -1).reduce((memo, current) => {
  4. return memo[current];
  5. }, rootState);
  6. Vue.set(parent, path[path.length - 1], module.state);
  7. }
  8. module.forEachMutation((mutation, key) => {
  9. store._mutations[key] = (store._mutations[key] || []);
  10. store._mutations[key].push((payload) => {
  11. mutation.call(store, module.state, payload);
  12. });
  13. });
  14. module.forEachAction((action, key) => {
  15. store._actions[key] = (store._actions[key] || []);
  16. store._actions[key].push(function(payload) {
  17. action.call(store, this, payload);
  18. });
  19. });
  20. module.forEachGetter((getter, key) => {
  21. store._wrappedGetters[key] = function() {
  22. return getter(module.state);
  23. }
  24. });
  25. module.forEachChild((child, key) => {
  26. installModule(store, rootState, path.concat(key), child)
  27. })
  28. }

对dispatch和 action方法进行重写

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


4.定义状态和计算属性

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

5.实现命名空间

  1. import { forEachValue } from '../util';
  2. import Module from './module';
  3. export default class ModuleCollection {
  4. getNamespace(path) {
  5. let module = this.root
  6. return path.reduce((namespace, key) => {
  7. module = module.getChild(key);
  8. console.log(module)
  9. return namespace + (module.namespaced ? key + '/' : '')
  10. }, '');
  11. }
  12. }
  13. export default class Module {
  14. get namespaced(){
  15. return !!this._rawModule.namespaced;
  16. }
  17. }

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

  1. function installModule(store, rootState, path, module) {
  2. let namespace = store._modules.getNamespace(path);
  3. if (path.length > 0) {
  4. let parent = path.slice(0, -1).reduce((memo, current) => {
  5. return memo[current];
  6. }, rootState);
  7. Vue.set(parent, path[path.length - 1], module.state);
  8. }
  9. module.forEachMutation((mutation, key) => {
  10. store._mutations[namespace + key] = (store._mutations[namespace + key] || []);
  11. store._mutations[namespace + key].push((payload) => {
  12. mutation.call(store, module.state, payload);
  13. });
  14. });
  15. module.forEachAction((action, key) => {
  16. store._actions[namespace + key] = (store._actions[namespace + key] || []);
  17. store._actions[namespace + key].push(function(payload) {
  18. action.call(store, this, payload);
  19. });
  20. });
  21. module.forEachGetter((getter, key) => {
  22. store._wrappedGetters[namespace + key] = function() {
  23. return getter(module.state);
  24. }
  25. });
  26. module.forEachChild((child, key) => {
  27. installModule(store, rootState, path.concat(key), child)
  28. })
  29. }

6.注册模块

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

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

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

销毁上次创建的实例

四.插件机制

1.使用方式

  1. function persists(store) { // 每次去服务器上拉去最新的 session、local
  2. let local = localStorage.getItem('VUEX:state');
  3. if (local) {
  4. store.replaceState(JSON.parse(local)); // 会用local替换掉所有的状态
  5. }
  6. store.subscribe((mutation, state) => {
  7. // 这里需要做一个节流 throttle lodash
  8. localStorage.setItem('VUEX:state', JSON.stringify(state));
  9. });
  10. }
  11. plugins: [
  12. persists
  13. ]

这里我们要实现subscribe、replaceState方法

  1. // 执行插件
  2. options.plugins.forEach(plugin => plugin(this));
  3. subscribe(fn){
  4. this._subscribers.push(fn);
  5. }
  6. replaceState(state){
  7. this._vm._data.$$state = state;
  8. }

2.获取最新状态

  1. function getState(store, path) {
  2. let local = path.reduce((newState, current) => {
  3. return newState[current];
  4. }, store.state);
  5. return local
  6. }
  7. module.forEachMutation((mutation, key) => {
  8. store._mutations[namespace + key] = (store._mutations[namespace + key] || []);
  9. store._mutations[namespace + key].push((payload) => {
  10. mutation.call(store, getState(store,path), payload);
  11. });
  12. });

调用mutation时传入最新状态

五.辅助函数

1.mapState实现

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

2.mapGetters实现

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

3.mapMutations实现

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

4.mapActions实现

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


六.区分mutation和action

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

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

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

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

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

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