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: { $$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 = {}) { this._mutations = options.mutations || {} } commit(type, payload) { const entry = this._mutations[type] if (!entry) { console.error(`unknown mutation type: ${type}`); return } 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 = {}) { this._actions = options.actions || {} } dispatch(type, payload) { const entry = this._actions[type] if (!entry) { console.error(`unknown action type: ${type}`); return } 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 => { const fn = store._getters[key] computed[key] = function () { return fn(store.state) } Object.defineProperty(store.getters, key, { get: () => store._vm[key] }) }) this._vm = new Vue({ data: { $$state: options.state }, computed }) } }
|