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),
});
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;
}
},
});
};
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;
}
}
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;
// 最终解析出来的就是树一样的结构
}
}
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
);
});
}
};
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);
});
}
}
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);
};
}
}
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 };
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;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24