The Future Depends on You
首页/Vue/手写 Vuex 核心功能/
手写 Vuex 核心功能
上次更新时间:2021-1-25 文章分类:Vue 阅读人数:17

1.Vuex 简介

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。接下来,我们实现一下 vuex 。

2.Vuex源码实现

目录结构:

--vuex
  --module
    module-collection.js
    module.js
  helpers.js // 辅助函数
  index.js   // vuex 入口
  mixin.js   // Vue.mixin
  store.js   // Store
  util.js    // 工具函数

工具函数:

vuex -> util.js

export const forEach = (obj = {}, fn) => {
  Object.keys(obj).forEach(key => fn(obj[key], key));
};

forEatch 函数用于遍历注册方法。

2.1.install方法

install 方法是每个 vue 插件都需要提供的方法。在 install 方法中,我们需要将用户传递的 store 传递给每个组件。

入口文件

import { Store, install } from './store';

export default {
    Store,
    install
}
export {
    Store,
    install
}

通过 Vue.mixinstore 传递给每个组件。

vuex -> mixin.js

const applyMixin = Vue => {
  Vue.mixin({
    beforeCreate: vuexInit
  });
};

// 组件的创建过程 先父后子
function vuexInit() {
  // vuex 给每个组件都定义一个$store属性 指向的是同一个人
  const options = this.$options;
  if (options.store) {
    // 根实例
    this.$store = options.store;
  } else if (options.parent && options.parent.$store) {
    // 子组件
    this.$store = options.parent.$store;
  }
}

export default applyMixin;

install 中引入 applyMixin

vuex -> store.js

let Vue; // eslint-disable-line no-unused-vars
class Store {
  constructor(options) {
  }
}

const install = _Vue => {
  Vue = _Vue;
  applyMixin(Vue);
};

export { Store, install };

2.2.vuex中state的实现

vuex 中将 state 放在了一个 Vue 实例上。

export class Store {
  constructor(options){
    let state = options.state;
    this._vm = new Vue({
      data: {
        $$state: state,
      }
    });
  }
  get state() {
    return this._vm._data.$$state
  }
}

2.3.getters实现

vuex 中的 getters 就相当于 Vue 中的 computed。vuex 中将 getters 代理到 computed

import { forEach } from "./util";

this.getters = {};
const computed = {}
forEach(options.getters, (fn, key) => {
    computed[key] = () => {
        return fn(this.state);
    }
    Object.defineProperty(this.getters,key,{
        get:()=> this._vm[key]
    })
});
this._vm = new Vue({
    data: {
        $$state: state,
    },
    computed // 利用计算属性实现缓存
});

2.4.actions和mutations

采用发布订阅模式将用户定义的 mutationaction 先保存起来,然后 当调用 commit 时就找订阅的 motation 方法,调用 dispatch 就找对应的 action 方法。

this._mutations = {};
forEach(options.mutations, (fn, key) => {
  this._mutations[key] = payload => fn.call(this, this.state, payload);
});
commit = (type, payload) => {
  this._mutations[type](payload);
};

this._actions = {};
forEach(options.actions, (fn, key) => {
  this._actions[key] = payload => fn.call(this, this, payload);
});
dispatch = (type, payload) => {
  this._actions[type](payload);
};

2.5.模块实现

在上面的代码中,我们实现了 state、getters、mutations 和 actions 的基础功能。但是在 vuex 中还有 mudules 模块的功能。上面的代码显然无法实现模块区分。所以我们需要区分不同的模块。

2.5.1.格式化用户数据

用户传递的数据:

let Store = new Vuex.Store({
  state: {
    age: 28
  },
  getters: {
    getAge(state) {
      return state.age + 10;
    }
  },
  mutations: {
    changeAge(state, payload) {
      state.age += payload;
    }
  },
  actions: {
    changeAge({ commit }, payload) {
      setTimeout(() => {
        commit("changeAge", payload);
      }, 1000);
    }
  },
  modules: {
    a: {
      namespaced: true,
      state: {
        c: 100
      },
      mutations: {
        changeAge() {
          console.log("c更新 ");
        }
      }
    },
    b: {
      namespaced: true,
      state: {
        d: 100
      },
      mutations: {
        changeAge() {
          console.log("d更新 ");
        }
      },
      modules: {
        c: {
          namespaced: true,
          state: {
            e: 400
          },
          mutations: {
            changeAge() {
              console.log("d更新 ");
            }
          }
        }
      }
    }
   }
});

我们需要将用户传递的数据转换成:

{
  this._rawModule = module;
  this.state = module.state;
  this._children = {
    this.state = xxx;
    this._rawModule = module;
    this.state = module.state;
    this._children = {
      this.state = xxx;
      this._rawModule = module;
      this.state = module.state;
      this._children = {};
    };
  };
}

vuex -> store.js

import moduleCollection from "./module/module-collection";

class Store {
  constructor(options) {
    // 格式化用户传入的参数,格式化成树形结构 更直观一些,后续也更好操作一些
    // 1.收集模块转换成一棵树
    this._modules = new moduleCollection(options);
  }
}

moduleCollection 中,采用递归的方式生成模块。 vuex -> module -> module-collection.js

import { forEach } from "../util";
import Module from "./modules";

export default class moduleCollection {
  constructor(options) {
    // 注册模块 递归注册 根模块
    console.log("options", options);
    this.register([], options);
  }
  register(path, rootModule) {
    let newModule = new Module(rootModule);
    rootModule.rawModule = newModule; //把当前要注册的模块上 做一个映射
    if (path.length === 0) {
      this.root = newModule;
    } else {
      let parent = path.slice(0, -1).reduce((memo, current) => {
        // return memo._children[current];
        return memo.getChild(current);
      }, this.root);
      // parent._children[path[path.length - 1]] = newModule;
      parent.addChild(path[path.length - 1], newModule);
    }
    // 如果有modules 说明有子模块
    if (rootModule.modules) {
      forEach(rootModule.modules, (module, moduleName) => {
        this.register([...path, moduleName], module);
      });
    }
  }
}

Module 类中定义每个module层级相关的数据。

import { forEach } from "../util";

export default class Module {
  constructor(rootModule) {
    this._rawModule = rootModule;
    this._children = {};
    this.state = rootModule.state;
  }
  getChild(key) {
    return this._children[key];
  }
  addChild(key, module) {
    this._children[key] = module;
  }
  // 当前模块的mutations
  forEachMutation(fn) {
    if (this._rawModule.mutations) {
      forEach(this._rawModule.mutations, fn);
    }
  }
  // 当前模块的actions
  forEachAction(fn) {
    if (this._rawModule.actions) {
      forEach(this._rawModule.actions, fn);
    }
  }
  // 当前模块的getters
  forEachGetters(fn) {
    if (this._rawModule.getters) {
      forEach(this._rawModule.getters, fn);
    }
  }
  forEachChild(fn) {
    forEach(this._children, fn);
  }
}

2.5.2.安装模块

vuex -> store.js

/**
 * @param {*} store 实例
 * @param {*} rootState 根状态
 * @param {*} path 路径
 * @param {*} module 模块
 */
function installModule(store, rootState, path, module) {
  // 注册事件时,需要注册到对应的命名空间中,path就是所有的路径 根据path算出一个空间里
  let namespace = store._modules.getNamespace(path);
  // 如果是子模块 需要将子模块的状态定义到根模块上
  if (path.length > 0) {
    let parent = path.slice(0, -1).reduce((memo, current) => {
      return memo[current];
    }, rootState);
  }
  module.forEachMutation((mutation, type) => {
    store._mutations[type] =
      store._mutations[type] || [];
    store._mutations[ type].push(payload => {
        mutation.call(store, module.state, payload); // 这里更改状态
    });
  });
  module.forEachAction((action, type) => {
    store._actions[type] = store._actions[ type] || [];
    store._actions[type].push(payload => {
      action.call(store, store, payload);
    });
  });
  module.forEachGetters((getter, key) => {
    // 如果getters 重名会覆盖,所有的模块的getters 都会定义到根模块上
    store._wrapperGetters[key] = function() {
      return getter(module.state);
    };
  });
  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child);
  });
}

class Store {
  constructor(options) {
    // 2.安装模块 将模块上的属性定义到store中
    installModule(this, state, [], this._modules.root);
  }
}

2.5.3.重写dispatch和action

commit = (type, payload) => {
  this._mutations[type].forEach(fn => fn(payload));
};
dispatch = (type, payload) => {
  this._actions[type].forEach(fn => fn(payload));
};

2.5.4.增加响应式效果

vuex -> store.js

function resetStoreVm(store, state) {
  let computed = {};
  store.getters = {};
  // 2.让getters 定义在store上
  forEach(wrapperGetters, (fn, key) => {
    // 通过 computed 实现缓存效果
    computed[key] = function() {
      return fn();
    };
    Object.defineProperty(store.getters, key, {
      get: () => store._vm[key]
    });
  });
  // 1. 实现让状态变成响应式
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  });
}

class Store {
  constructor(options) {
    // 2.安装模块 将模块上的属性定义到store中
    installModule(this, state, [], this._modules.root);
  }
}

2.6.命名空间

获取每个模块的 namespaced 属性

vuex -> module -> module.js

export default class Module {
  // 属性访问器
  get namespaced() {
    return this._rawModule.namespaced;
  }
}

计算命名空间

vuex -> module -> mudule-collection.js

export default class moduleCollection {
  // 计算命名空间
  getNamespace(path) {
    let root = this.root;
    return path.reduce((namespace, key) => {
      root = root.getChild(key);
      return namespace + (root.namespaced ? key + "/" : "");
    }, "");
  }
}

添加命名空间

vuex -> store.js

function installModule(store, rootState, path, module) {
  // 注册事件时,需要注册到对应的命名空间中,path就是所有的路径 根据path算出一个空间里
  let namespace = store._modules.getNamespace(path);
  if (path.length > 0) {
    let parent = path.slice(0, -1).reduce((memo, current) => {
      return memo[current];
    }, rootState);
  }
  module.forEachMutation((mutation, type) => {
    store._mutations[namespace + type] = store._mutations[namespace + type] || [];
    store._mutations[namespace + type].push(payload => {
        mutation.call(store, module.state, payload);
    });
  });
  module.forEachAction((action, type) => {
    store._actions[namespace + type] = store._actions[namespace + type] || [];
    store._actions[namespace + type].push(payload => {
      action.call(store, store, payload);
    });
  });
  module.forEachGetters((getter, key) => {
    store._wrapperGetters[namespace + key] = function() {
      return getter(module.state);
    };
  });
  module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child);
  });
}

2.7.模块注册

有时候模块需要动态的添加

vuex -> store.js

class Store {
  registerModule(path, rawModule) {
    // path 需要是一个数组
    if (typeof path == "string") path = [path];
    // 模块注册
    this._modules.register(path, rawModule);
    // 安装模块 动态的将状态新增上去
    installModule(this, this.state, path, rawModule.rawModule);
    // 重新定义 getters
    resetStoreVm(this, this.state);
  }
}

销毁上次创建的实例

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

  let computed = {};
  store.getters = {};
  forEach(wrapperGetters, (fn, key) => {
    computed[key] = function() {
      return fn();
    };
    Object.defineProperty(store.getters, key, {
      get: () => store._vm[key]
    });
  });
  store._vm = new Vue({
    data: {
      $$state: state
    },
    computed
  });
  // 销毁老的实例
  + if (oldVm) {
  +   Vue.nextTick(() => oldVm.$destroy());
  + }
}

2.8.vuex插件

使用方式

let Store = new Vuex.Store({
  // ...
  plugins: [ persists ],
  // ...
}

持久化插件

function persists(store) {
  let local = localStorage.getItem("VUEX:STATE");
  if (local) {
    store.replaceState(JSON.parse(local));
  }
  store.subscribe((mutation, state) => {
    localStorage.setItem("VUEX:STATE", JSON.stringify(state));
  });
}

vuex -> store.js

class Store {
  constructor(options) {
    this._subscribers = [];
    // 插件的实现
    options.plugins.forEach(plugin => plugin(this));
  }
  subscribe(fn) {
    this._subscribers.push(fn);
  }
  // 用最新的状态替换掉
  replaceState(newState) {
    this._vm._data.$$state = newState;
  }
}

获取最新状态

vuex -> store.js

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

module.forEachMutation((mutation, type) => {
  store._mutations[namespace + type] = store._mutations[namespace + type] || [];
  store._mutations[namespace + type].push(payload => {
    // 传入最新状态
    mutation.call(store, getState(store, path), payload);
  });
});

2.9.区分motation和action

当 vuex 采用 严格模式(strict) 时,只能在 mutation 中修改 state 的数据。

vuex -> store.js

class Store {
  constructor(options) {
    // ...
    // 同步的watcher
    this._committing = false;
  }
  _withCommitting(fn) {
    let committing = this._committing;
    // 在函数调用前 表示 _committing为true
    this._committing = true;
    fn();
    this._committing = committing;
  }
}

function resetStoreVm(store, state) {
  // ...
  if (store.strict) {
    // 只要状态变化会立即执行,在状态变化后同步执行
    store._vm.$watch(
      () => store._vm._data.$$state,
      () => {
        console.assert(store._committing, "在mutation之外更改了 state");
      },
      { deep: true, sync: true }
    );
  }
  // ...
}

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

function installModule(store, rootState, path, module) {
  let namespace = store._modules.getNamespace(path);
  if (path.length > 0) {
    let parent = path.slice(0, -1).reduce((memo, current) => {
      return memo[current];
    }, rootState);
    // Vue.set 这个api 可以新增属性 如果本身对象不是响应式会直接复制
   + store._withCommitting(() => {
       Vue.set(parent, path[path.length - 1], module.state);
   + });
  }
  module.forEachMutation((mutation, type) => {
    store._mutations[namespace + type] =
      store._mutations[namespace + type] || [];
    store._mutations[namespace + type].push(payload => {
      // 内部可能会替换状态,这里如果一直使用module.state,可能就是老的状态
    +  store._withCommitting(() => {
         mutation.call(store, getState(store, path), payload); // 这里更改状态
    +  });
      store._subscribers.forEach(sub => sub({ mutation, type }, store.state));
    });
  });
}

// 用最新的状态替换掉
replaceState(newState) {
  this._withCommitting(() => {
    this._vm._data.$$state = newState;
  });
}

2.10.辅助函数

vuex -> helpers.js

2.10.1.mapState

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

2.10.2.mapGetters

export const mapGetters = arrList => {
  let obj = {};
  for (let i = 0; i < arrList.length; i++) {
    let stateName = arrList[i];
    obj[stateName] = function() {
      return this.$store.getters[stateName];
    };
  }
  return obj;
};

2.10.3.mapMutations

export const mapMutations = arrList => {
  let obj = {};
  for (let i = 0; i < arrList.length; i++) {
    let stateName = arrList[i];
    obj[stateName] = function(payload) {
      this.$store.commit[(stateName, payload)];
    };
  }
  return obj;
};

2.10.4.mapActions

export const mapActions = arrList => {
  let obj = {};
  for (let i = 0; i < arrList.length; i++) {
    let stateName = arrList[i];
    obj[stateName] = function(payload) {
      this.$store.dispatch[(stateName, payload)];
    };
  }
  return obj;
};

源码地址:https://github.com/Tangyincheng/vuex-core

  • 完结.