回顾下 Vuex
先 vue-cli 工具直接创建一个项目,勾选 Vuex,其他随意:
创建完毕自动安装依赖,之后启动项目,熟悉的 helloworld ~ 简单写个 demo 运行看看,后面会逐步实现一个 myVuex,来达到相同的期望运行结果:
src/store/index.js :
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: { // vuex约定了对state的操作函数都放在这里,使用commit方法触发 changeAge(state, data) { state.age = data ? data : ++state.age } }, actions: { // vuex约定了异步类函数统一放在这里,dispatch方法触发 syncChangeAge({ state, commit }, data) { state.age = 0 setTimeout(() => { this.commit('changeAge', data) // 这里我还没弄懂待会怎么实现{commit}的读取,在真实的Vuex中这里不加this也是可以运行的 }, 1000); } }, modules: { /** vuex的模块化,先不实现modules功能,就不挖坑了 */ },});
复制代码
src/App.vue :
<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 :
class Store { constructor(parameters) { // vuex的核心四件套 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 :
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 :
let _Vue
class Store { constructor(parameters) { const { state = { } } = parameters this.$vue = new _Vue({ // new一个Vue实例接收用户传进的state data: { state } }) ...... } get state() { // 抛出Vue实例上挂载的 state return this.$vue.state } ......}
const install = (Vue) => { _Vue = Vue ......}.....
复制代码
处理 getter
继续上面的代码
....class Store { constructor(parameters) { ..... bindInstall(this, parameters) } .....}
const install = (Vue) => { .... }
const bindInstall = (store, options) => { // 处理getters 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 中的引入改成我们自己的了:
// import Vuex from "vuex";import Vuex from "../Vuex";.....
复制代码
将例子运行,将看到已经成功拿到 store 中的 getter,继续完善
处理 mutations 与 actions
继续完善刚才的 bindInstall 代码:
....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) // mutations中的函数第一个参数是state,第二个是值 }) 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) // 这里我第一个参数先直接返回了实例对象,还不知道如何实现vuex中的效果 }) store.actions[actionName] = storeActions }) }}export default { Store, install }
复制代码
保存,运行测试 - 和最初的 demo 结果一致,至此实现了核心的 vuex 状态管理器
以下是 Vuex/index.js 完整代码
let _Vueclass 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 this.$store = store ? store : this.$parent ? this.$parent.$store : null } })}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 }
复制代码
评论