CD's blog CD's blog
首页
  • HTMLCSS
  • JavaScript
  • Vue
  • TypeScript
  • React
  • Node
  • Webpack
  • Git
  • Nestjs
  • 小程序
  • 浏览器网络
  • 学习笔记

    • 《TypeScript 从零实现 axios》
    • Webpack笔记
  • JS/TS教程

    • 《现代JavaScript》教程
🔧工具方法
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

CD_wOw

内卷的行情,到不了的梦
首页
  • HTMLCSS
  • JavaScript
  • Vue
  • TypeScript
  • React
  • Node
  • Webpack
  • Git
  • Nestjs
  • 小程序
  • 浏览器网络
  • 学习笔记

    • 《TypeScript 从零实现 axios》
    • Webpack笔记
  • JS/TS教程

    • 《现代JavaScript》教程
🔧工具方法
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Vue2基础及原理

  • Vue3基础及原理

  • 周边工具

    • vuex 的简单实现
      • vuex 的基本使用
  • Vue笔记
  • 周边工具
CD
2021-07-07
目录

vuex 的简单实现

# vuex 的基本使用

vuex 在我看来,实际上是一段协定好的方法和入参,通过将其全局注册在 vue 之后处理相应的事情。接下来,我们通过一段代码看下 vuex 在 vue 中的注册流程

// vuex/index.js
import Vue from "vue";
import Vuex from "vuex";
import getters from "./getters";

// 通过调用use方法将vuex注册
Vue.use(Vuex);
const modulesFiles = require.context("./modules", true, /\.js$/);
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
  const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, "$1");
  const value = modulesFiles(modulePath);
  modules[moduleName] = value.default;
  return modules;
}, {});
// 创建store实例
const store = new Vuex.Store({ modules, getters });

// main.js
// 将store注入 new Vue 实例
import store from "./store";
new Vue({
  el: "#BYUI_APP",
  router,
  store,
  render: (h) => h(App),
});
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

vue 下的插件和 vue 都有一个约定,通过编写 install 函数来为 vue 注入插件。在 vue.use 执行的时候会将 Vue 变量传递给 install 函数,这个时候拿到的就是一个将会 mount 到指定位置的 vue 实例,因此可以通过这个 vue 实例为其添加全局变量或是方法

// vuex.js
let _Vue;
const install = (Vue) => {
  // 我们将传入的vue实例在本地保存一份
  _Vue = Vue;
  Vue.mixin({
    beforeCreated() {
      const { store = "" } = this.$options;
      if (store) {
        this.$store = store;
      } else if (this.$options.parent && this.$options.parent.$store) {
        // 组件内部没有设定store,则从根App.vue下继承$store方法
        this.$store = this.$options.parent.$store;
      }
    },
  });
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

当我们在实例的生命周期中添加$store 实例后,我们接下来创建 store 的类

// vuex.js
class Store {
  constructor(options) {
    const { state = {} } = options;

    // 通过_vue创建vue实例
    this._state = new _Vue({
      data: {
        state: state,
      },
    });

    this.getters = {};
    this.mutations = {};
    this.actions = {};
  }

  // 将vue实例中的state暴露出去
  get state() {
    return this._state.state;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

至此,我们通过先前在 use 里传入的 Vue 创建了一个新的 Vue 实例,并将 new Store 中传入的 state 作为 data 存放在该实例,当该实例中的 state 发生改变的时候,就会触发 vue 双向绑定的监听事件,从而使得数据和视图层发生改变。但从而也衍生了一个新的问题:如果我仅仅是通过对这个 Store 中的 state 进行修改,在不同的地方都进行了肆意的改动时,整个 state 的变化就会变得难以预测,所以 vuex 对此与用户们做出了一个约定,通过 mutations 或是 actions 内的方法实现数据的更改。 那么现在我们一个能拿到 state 的最简单的类的模型已经搭好了,接下来我们需要去书写 vuex 一个特殊的模块:modules。

modules 是一个 object 类型的模块,每一个 modules 都是一个单独的 vuex 模块,并且 modules 中也可能存在着再嵌套 modules 的关系。接下来我们就根据其特性开始写一个 moduels 的类

// modules.js

export default class ModulesCollection {
  constructor(options) {
    // 由于每一个modules都会不同,我们需要用一个变量去记住它们。我们约定传入的options是一个vuex传入的格式的模块
    this.root = this.register(options);
  }

  register(options) {
    //我们约定好每一个modules解析出来的格式
    let module = {
      state: options.state,
      _rawModule: options,
      _children: {
        // 遍历后的结构:
        // state: options.state,
        // _rawModule: options,
        // _children: {
        //   state: options.state,
        //   _rawModule: options,
        //   _children: {},
        // },
      },
    };

    // 如果存在module嵌套,把它们解析成相同的结构放到_children中
    if (options.modules) {
      Object.keys(options.modules).forEach((key) => {
        const childModule = this.register(options.modules[key]);
        module._children[key] = childModule;
      });
    }

    return 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
29
30
31
32
33
34
35
36
37

接下来,我们将模块的类引入到 vuex 文件中做处理

// vuex.js
import ModulesCollection from "./modules.js";
class Store {
  constructor(options) {
    const { state = {} } = options;

    this._state = new _Vue({
      data: { state: state },
    });

    this.getters = {};
    this.mutations = {};
    this.actions = {};
    // 解析模块层次成我们约定好的格式
    this.modules = new ModulesCollection(options);
    // 注册对应的模块, 我们通过创建ModulesCollection,其root属性就是我们处理好的对象
    installModules(this, this.state, this.modules.root, []);
  }
  get state() {
    return this._state.state;
  }
}

const installModules = (store, state, rootModules, path) => {
  const { getters, mutations, actions } = rootModules._rawModule;

  // 处理getters
  if (getters) {
    Object.keys(getters).forEach((key) => {
      // getters在平时使用中会以变量:$store.getters.xxx 的形式直接使用,这里我们需要将其做监听在调用时将结果执行后回调。这里我们监听的是该store根实例上的getters,让其拥有所有getters的方法, 这里getters有重名会被覆盖
      Object.defineProperty(store.getters, key, {
        get() {
          return getters[key](rootModules.state);
        },
      });
    });
  }

  // 处理mutations
  if (mutations) {
    Object.keys(mutations).forEach((mutationsName) => {
      // 拿到当前实例下的mutations 由于mutations可能存在重名,所以每个key用数组来进行存放,执行mutations时遍历数组下的函数执行
      const currentMutation = store.mutations[mutationsName] || [];
      currentMutation.push((payload) => {
        mutations[mutationsName].call(store, rootModules.state, payload);
      });
      store.mutations[mutationsName] = currentMutation;
    });
  }

  // 处理actions 同理
  if (actions) {
    Object.keys(actions).forEach((actionName) => {
      let storeActions = store.actions[actionName] || [];
      storeActions.push((payload) => {
        actions[actionName].call(
          store,
          {
            commit: store.commit,
            state: rootModules.state,
          },
          payload
        );
      });
      store.actions[actionName] = storeActions;
    });
  }

  // 如果有嵌套的modules 递归执行
  if (rootModules._children) {
    Object.keys(rootModules._children).forEach((childrenModuleName) => {
      installModules(
        store,
        state,
        rootModules._children[childrenModuleName],
        path.concat(childrenModuleName),
        childrenModuleName
      );
    });
  }
};
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81

至此,一个简易的 vuex 就差不多完成了,接下来我们将 commit 和 dispath 方法补充上去,其实质就是找到相应的 mutations、actions 函数执行

// vuex.js
class Store {
  commit(mutationName, payload) {
    this.mutations[mutationName].forEach((mutation) => {
      mutation(payload);
    });
  }
  dispatch(actionName, payload) {
    this.actions[actionName].forEach((action) => {
      action(payload);
    });
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

做到这里,其实会有一个小 BUG,就是在 action 内调用异步函数的时候,其内部的 commit 方法会失去 this 指向,所以我们需要在 constructor 内添加这么一段代码

class Store {
  constructor(options) {
    const { dispatch, commit } = this;
    this.commit = (name, payload) => {
      return commit.call(this, name, payload);
    };
    this.dispatch = (name, payload) => {
      return dispatch.call(this, name, payload);
    };
  }
}
1
2
3
4
5
6
7
8
9
10
11

最后完整代码如下:

// store.js
import ModulesCollection from "./modules";

let _Vue;

class Store {
  constructor(options) {
    const { state = {} } = options;

    this._state = new _Vue({
      data: { state: state },
    });
    const { dispatch, commit } = this;
    this.commit = (name, payload) => {
      return commit.call(this, name, payload);
    };
    this.dispatch = (name, payload) => {
      return dispatch.call(this, name, payload);
    };
    this.getters = {};
    this.mutations = {};
    this.actions = {};
    // 解析模块层次
    this.modules = new ModulesCollection(options);
    // 注册对应的模块
    installModules(this, this.state, this.modules.root, []);
  }
  // 暴露state
  get state() {
    return this._state.state;
  }

  commit(mutationName, payload) {
    this.mutations[mutationName].forEach((mutation) => {
      mutation(payload);
    });
  }
  dispatch(actionName, payload) {
    this.actions[actionName].forEach((action) => {
      action(payload);
    });
  }

  //   forEachGetters(getters, cb) {
  //     Object.keys(getters).forEach(key => {
  //       cb(key, getters[key])
  //     })
  //   }
}

// 插件的install函数
const install = (v) => {
  _Vue = v;
  v.mixin({
    beforeCreate() {
      const { store = "" } = this.$options;
      if (store) {
        this.$store = store;
      } else {
        this.$store = this.$parent && this.$parent.$store;
      }
    },
  });
};

const installModules = (store, state, rootModules, path) => {
  // 处理getters
  const { getters, mutations, actions } = rootModules._rawModule;
  if (getters) {
    Object.keys(getters).forEach((key) => {
      Object.defineProperty(store.getters, key, {
        get() {
          return getters[key](rootModules.state);
        },
      });
    });
  }

  // 处理mutations
  if (mutations) {
    Object.keys(mutations).forEach((mutationName) => {
      let storeMutations = store.mutations[mutationName] || [];
      storeMutations.push((payload) => {
        mutations[mutationName].call(store, rootModules.state, payload);
      });
      store.mutations[mutationName] = storeMutations;
    });
  }
  // 处理actions
  if (actions) {
    Object.keys(actions).forEach((actionName) => {
      let storeActions = store.actions[actionName] || [];
      storeActions.push((payload) => {
        actions[actionName].call(
          store,
          {
            commit: store.commit,
            state: rootModules.state,
          },
          payload
        );
      });
      store.actions[actionName] = storeActions;
    });
  }

  if (rootModules._children) {
    Object.keys(rootModules._children).forEach((childrenModuleName) => {
      installModules(
        store,
        state,
        rootModules._children[childrenModuleName],
        path.concat(childrenModuleName),
        childrenModuleName
      );
    });
  }
  //   console.log(store)
};

export default { install, Store };
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
//modules
class ModulesCollection {
  constructor(options) {
    this.root = this.register(options);
  }
  register(options) {
    let module = {
      state: options.state,
      _rawModule: options,
      _children: {},
    };

    if (options.modules) {
      Object.keys(options.modules).forEach((key) => {
        const childModule = this.register(options.modules[key]);
        module._children[key] = childModule;
      });
    }

    return module;
  }
}

export default ModulesCollection;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
编辑 (opens new window)
#Vue2
上次更新: 2023/03/22, 15:28:00
Vue3的patch原理

← Vue3的patch原理

最近更新
01
gsap动画库学习笔记 - 持续~
06-05
02
远程组件加载方案笔记
05-03
03
小程序使用笔记
03-29
更多文章>
Theme by Vdoing | Copyright © 2020-2023 CD | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式