前言
先vue-cli工具直接创建一个项目,勾选Vuex,其他随意:
创建完毕自动安装依赖,之后启动项目,熟悉的helloworld ~ 简单写个demo运行看看,后面会逐步实现一个myVuex,来达到相同的期望运行结果:
src/store/index.js
:
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
| import Vue from "vue"; import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({ state: { age: 7 }, getters: { getAge(state) { return state.age } }, mutations: { changeAge(state, data) { state.age = data ? data : ++state.age } }, actions: { syncChangeAge({ state, commit }, data) { state.age = 0 setTimeout(() => { this.commit('changeAge', data) }, 1000); } }, modules: { }, });
|
src/App.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <template> <div id="app"> {{ showMe }} <button @click="$store.commit("changeAge")">increase</button> <button @click="$store.dispatch("syncChangeAge", 7)">reset</button> </div> </template>
<script lang="js"> import Vue from "vue"; export default Vue.extend({ name: "App", computed: { showMe() { return `我今年${this.$store.getters.getAge || "..."}岁了`; }, } }); </script>
|
运行效果如下:
说明:点击增加按钮加一岁,点击重置按钮进入loading状态1秒后又设置为7岁,现在,把stroe中引入的import Vuex from "vuex";
改为自己的手动实现,达到跟这个demo一致的运行效果。
Ready Perfect
开始前还是先写出代码结构,创建 Vuex
文件夹,写入第一个index文件。
src/Vuex/index.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class Store { constructor(parameters) { this.state = {} this.getters = {} this.mutations = {} this.actions = {} } get state() { return this.state } commit(fName, data) { this.mutations[fName].forEach(mutation => mutation(data)); } dispatch(fName, data) { this.actions[fName].forEach(action => action(data)); } }
export default { Store }
|
这样vuex的简单结构就写完了,接下来处理对实例传入的mutation和action的收集,然后提供commit和dispatch函数来执行。
创建 install
首先 store 中先是调用了 Vue.use(Vuex)
,让状态管理器注入到vue中,此时需要用到混入。
mixin 参考:vue全局混入
根据vue文档描述,使用use必须提供一个install函数,Vue会作为参数传入,参考:vueUse
src/Vuex/index.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Store { ..... }
const install = (Vue) => { Vue.mixin({ beforeCreate() { const { store = null } = this.$options if (store) { this.$store = store } else { this.$store = this.$parent && this.$parent.$store } } }) }
export default { Store, install }
|
绑定 state
在上一步创建install时引入了Vue,将其挂载到全局来创建一个实例对象,利用Vue中数据双向绑定来实现state:
src/Vuex/index.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| let _Vue
class Store { constructor(parameters) { const { state = { } } = parameters this.$vue = new _Vue({ data: { state } }) ...... } get state() { return this.$vue.state } ...... }
const install = (Vue) => { _Vue = Vue ...... } .....
|
处理 getter
继续上面的代码
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
| .... class Store { constructor(parameters) { ..... bindInstall(this, parameters) } ..... }
const install = (Vue) => { .... }
const bindInstall = (store, options) => { const { getters } = options if (getters) { Object.keys(getters).forEach(key => { Object.defineProperty(store.getters, key, { get() { return getters[key](options.state) } }) }) } }
export default { Store, install }
|
到这里,可以将 src/store/index
中的引入改成我们自己的了:
1 2 3
| import Vuex from "../Vuex"; .....
|
将例子运行,将看到已经成功拿到store中的getter,继续完善
处理 mutations 与 actions
继续完善刚才的bindInstall代码:
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
| .... class Store { ..... }
const install = (Vue) => { .... }
const bindInstall = (store, options) => { const { getters, mutations, actions } = options if (getters) { ... } if (mutations) { Object.keys(mutations).forEach(mutationName => { let storeMutations = store.mutations[mutationName] || [] storeMutations.push(data => { mutations[mutationName].call(store, store.state, data) }) store.mutations[mutationName] = storeMutations }) } if (actions) { Object.keys(actions).forEach(actionName => { let storeActions = store.actions[actionName] || [] storeActions.push(data => { actions[actionName].call(store, store, data) }) store.actions[actionName] = storeActions }) } } export default { Store, install }
|
保存,运行测试 - 和最初的demo结果一致,至此实现了核心的vuex状态管理器
以下是 Vuex/index.js
完整代码
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
| let _Vue
class Store { constructor(parameters) { const { state = {} } = parameters this.$vue = new _Vue({ data: { state } }) this.getters = {} this.mutations = {} this.actions = {} bindInstall(this, parameters) } get state() { return this.$vue.state } commit(fName, data) { this.mutations[fName].forEach(mutation => mutation(data)); } dispatch(fName, data) { this.actions[fName].forEach(action => action(data)); } }
const install = (Vue) => { _Vue = Vue Vue.mixin({ beforeCreate() { const { store = null } = this.$options if (store) { this.$store = store } else { this.$store = this.$parent && this.$parent.$store } } }) }
const bindInstall = (store, options) => { const { getters, mutations, actions } = options if (getters) { Object.keys(getters).forEach(key => { Object.defineProperty(store.getters, key, { get() { return getters[key](options.state) } }) }) } if (mutations) { Object.keys(mutations).forEach(mutationName => { let storeMutations = store.mutations[mutationName] || [] storeMutations.push(data => { mutations[mutationName].call(store, store.state, data) }) store.mutations[mutationName] = storeMutations }) } if (actions) { Object.keys(actions).forEach(actionName => { let storeActions = store.actions[actionName] || [] storeActions.push(data => { actions[actionName].call(store, store, data) }) store.actions[actionName] = storeActions }) } }
export default { Store, install }
|