Vuex原理及简单实现

Vuex

Vuex集中式存储管理应⽤的所有组件的状态,并以相应的规则保证状态以可预测的⽅式发⽣变化。

Vuex知识点回顾

核心概念

  • state 状态、数据

  • getters 派生状态

  • mutations 更改状态的函数

  • actions 异步操作

  • store 包含以上概念的容器

state 保存应用状态

1
2
3
export default new Vuex.Store({
state: { counter:0 },
})

mutations 修改状态

1
2
3
4
5
6
7
export default new Vuex.Store({
mutations: {
add(state) {
state.counter++
}
}
})

getters 从state派⽣出新状态,类似计算属性

1
2
3
4
5
6
7
export default new Vuex.Store({
getters: {
doubleCounter(state) { // 计算剩余数量
return state.counter * 2;
}
}
})

actions 添加业务逻辑

1
2
3
4
5
6
7
8
9
export default new Vuex.Store({
actions: {
add({ commit }) {
setTimeout(() => {
commit('add')
}, 1000);
}
}
})

测试代码

1
2
3
<p @click="$store.commit('add')">counter: {{$store.state.counter}}</p>
<p @click="$store.dispatch('add')">async counter: {{$store.state.counter}}</p>
<p>double:{{$store.getters.doubleCounter}}</p>

任务分析

  • 实现插件
    • 实现Store类
      • 维持⼀个响应式状态state
      • 实现commit()
      • 实现dispatch()
      • 实现getters
    • 挂载$store

实现过程

实现插件、state、挂载$store

因store配置时导出的是new Vuex.Store实例,因此实现插件时,创建的是Store类,并同时导出Store类和install方法。

同时,由于需要实现响应式的state,在这里借用定义Vue实例中的data来实现。

然后再利用mixin,将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
let Vue;

class Store {
constructor(options) {
// 响应式处理的数据
this.state = new Vue({
data: options.state
})
}

}

function install(_Vue) {
Vue = _Vue
Vue.mixin({
beforeCreate(){
if (this.$options.store) {
Vue.prototype.$store = this.$options.store
}
}
})
}

export default { Store, install };

但这样写就将vue实例直接暴露出去了,导致用户能够轻易访问到并对他为所欲为。

因此需要将这个vue实例隐藏起来,避免用户直接访问

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
let Vue;
class Store {
constructor(options = {}) {
this._vm = new Vue({
data: {
// 添加$$, Vue就不会代理
$$state:options.state
}
});
}
get state() {
return this._vm._data.$$state
}
set state(v) {
console.error('please use replaceState to reset state');
}
}
function install(_Vue) {
Vue = _Vue;

Vue.mixin({
beforeCreate() {
if (this.$options.store) {
Vue.prototype.$store = this.$options.store;
}
}
});
}
export default { Store, install };

commit

根据⽤户传⼊type获取并执⾏对应mutation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Store {
constructor(options = {}) {
// 保存⽤户配置的mutations选项
this._mutations = options.mutations || {}
}
commit(type, payload) {
// 获取type对应的mutation
const entry = this._mutations[type]
if (!entry) {
console.error(`unknown mutation type: ${type}`);
return
}
// 指定上下⽂为Store实例
// 传递state给mutation
entry(this.state, payload);
}
}

dispatch

根据⽤户传⼊type获取并执⾏对应action

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Store {
constructor(options = {}) {
// 保存⽤户编写的actions选项
this._actions = options.actions || {}
}
dispatch(type, payload) {
// 获取⽤户编写的type对应的action
const entry = this._actions[type]
if (!entry) {
console.error(`unknown action type: ${type}`);
return
}
// 异步结果处理常常需要返回Promise
return entry(this, payload);
}
}

然而测试时发现会报错_mutation is undefined

原来是因为当actions中调用commit时,this的指向可能不再是store实例了。调用acition同理。因此,需要将this绑定一下:

1
2
3
4
5
6
7
class Store {
constructor(options = {}) {
this.commit = this.commit.bind(this)
this.dispatch = this.dispatch.bind(this)
}
}

getters

由于getters与计算属性类似,因此也可以采用借鸡生蛋的方法,借用Vue实例中的computed来实现getters

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
class Store {
constructor(options) {
this._getters = options.getters;

let computed = {}
this.getters = {}
const store = this
Object.keys(this._getters).forEach(key => {
// 获取用户定义的getter
const fn = store._getters[key]
// 转换为computed无参数形式
computed[key] = function () {
return fn(store.state)
}
// 为getters定义只读
Object.defineProperty(store.getters, key, {
get: () => store._vm[key]
})
})

this._vm = new Vue({
data: {
$$state: options.state
},
computed
})

}

}