本文适合使用过 Vuex 的人阅读,来了解下怎么自己实现一个 Vuex。
基本骨架
这是本项目的src/store/index.js文件,看看一般 vuex 的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
   | import Vue from 'vue' import Vuex from './myvuex'  import * as getters from './getters' import * as actions from './actions' import state from './state' import mutations from './mutations'
  Vue.use(Vuex) 
 
  export default new Vuex.Store({   state,   mutations,   actions,   getters, })
   | 
Vue.use的用法:
安装 Vue.js 插件。如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象。
- 该方法需要在调用 new Vue() 之前被调用。
 - 当 install 方法被同一个插件多次调用,插件将只会被安装一次。
 
即是我们需要在./myvuex.js中导出 install方法,同时导出一个类Store,于是第一步可以写出代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | let Vue = null
  class Store {   constructor(options) {} }
  function install(_Vue) {   Vue = _Vue  }
  export default {   Store,   install, }
   | 
install 方法
当我们使用 vuex 的时候,每一个组件上面都有一个this.$store属性,里面包含了 state,mutations, actions, getters 等,所以我们也需要在每个组件上都挂载一个$store 属性,要让每一个组件都能获取到,这里我们使用Vue.mixin(mixin),用法介绍如下:
全局注册一个混入,影响注册之后所有创建的每个 Vue 实例。可以使用混入向组件注入自定义的行为,它将影响每一个之后创建的 Vue 实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
   | function install(_Vue) {   Vue = _Vue       Vue.mixin({     beforeCreate() {              if (this.$options && this.$options.store) {                  this.$store = this.$options.store       } else if (this.$parent && this.$parent.$store) {                  this.$store = this.$parent.$store       }     },   }) }
  | 
state
由于 Vuex 是基于 Vue 的响应式原理基础,所以我们要让数据改变可刷新视图,则需要创建一个 vue 实例
由于
1 2 3 4 5 6 7 8 9 10 11 12 13
   | class Store {      constructor(options) {          let vm = new Vue({       data: {         state: options.state,       },     })          this.state = vm.state   } }
  | 
commit
我们使用 vuex 改变数据时,是触发 commit 方法,即是这样使用的:
1
   | this.$store.commit('eventName', '参数' );
  | 
所以我们要实现一个commit方法,把 Store 构造函数传入的 mutations 做下处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
   | class Store {   constructor(options) {     
           this.mutations = {}      let mutations = options.mutations || {}          Object.keys(mutations).forEach(key => {       this.mutations[key] = params => {         mutations[key].call(this, this.state, params)        }     })   }
    commit = (key, params) => {          this.mutations[key](params)   } }
  | 
dispatch
跟上面的 commit 流程同理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
   | class Store {   constructor(options = {}) {     
           this.actions = {}     let actions = options.actions || {}     Object.keys(actions).forEach(key => {       this.actions[key] = params => {         actions[key].call(this, this, params)       }     })   }
    dispatch = (type, payload) => {     this.actions[type](payload)   } }
  | 
getters
getters 实际就是返回 state 的值,在使用的时候是放在 computed 属性,每一个 getter 都是函数形式;
getters 是需要双向绑定的。但不需要双向绑定所有的 getters,只需要绑定项目中事件使用的 getters。
这里使用Object.defineProperty()方法,它会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
   | class Store {   constructor(options = {}) {     
           this.getters = {}     let getters = options.getters || {}     Object.keys(getters).forEach(key => {       Object.defineProperty(this.getters, key, {         get: () => {           return getters[key].call(this, this.state)         },       })     })   } }
  | 
到此为止,已经可以使用我们自己写的 vuex 做一些基本操作了,但只能通过this.$store.xx的形式调用,故需要再实现方法。
map 辅助函数
先来说说 mapState
没有 map 辅助函数之前这样使用:
1 2 3 4 5
   | computed: {   count () {     return this.$store.state.count   } }
  | 
当映射的计算属性的名称与 state 的子节点名称相同时,给 mapState 传一个字符串数组。
1 2 3 4
   | computed: {      ...mapState(['count']) }
  | 
我们这里简单就只实现数组的情况
1 2 3 4 5 6 7 8 9
   | export const mapState = args => {   let obj = {}   args.forEach(item => {     obj[item] = function () {       return this.$store.state[item]     }   })   return obj }
  | 
之后几个 map 辅助函数都是类似
1 2 3 4 5 6 7 8 9
   | export const mapGetters = args => {   let obj = {}   args.forEach(item => {     obj[item] = function () {       return this.$store.getters[item]     }   })   return obj }
  | 
1 2 3 4 5 6 7 8 9
   | export const mapMutations = args => {   let obj = {}   args.forEach(item => {     obj[item] = function (params) {       return this.$store.commit(item, params)     }   })   return obj }
  | 
1 2 3 4 5 6 7 8 9
   | export const mapActions = args => {   let obj = {}   args.forEach(item => {     obj[item] = function (payload) {       return this.$store.dispatch(item, payload)     }   })   return obj }
  | 
完整代码
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 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
   | let Vue = null
  class Store {   constructor(options) {          let vm = new Vue({       data: {         state: options.state,       },     })          this.state = vm.state
           this.mutations = {}      let mutations = options.mutations || {}     Object.keys(mutations).forEach(key => {       this.mutations[key] = params => {         mutations[key].call(this, this.state, params)       }     })
           this.actions = {}     let actions = options.actions || {}     Object.keys(actions).forEach(key => {       this.actions[key] = params => {         actions[key].call(this, this, params)       }     })
           this.getters = {}     let getters = options.getters || {}     Object.keys(getters).forEach(key => {       Object.defineProperty(this.getters, key, {         get: () => {           return getters[key].call(this, this.state)         },       })     })   }
    commit = (key, params) => {     this.mutations[key](params)   }
    dispatch = (type, payload) => {     this.actions[type](payload)   } }
  export const mapState = args => {   let obj = {}   args.forEach(item => {     obj[item] = function () {       return this.$store.state[item]     }   })   return obj }
  export const mapGetters = args => {   let obj = {}   args.forEach(item => {     obj[item] = function () {       return this.$store.getters[item]     }   })   return obj }
  export const mapMutations = args => {   let obj = {}   args.forEach(item => {     obj[item] = function (params) {       return this.$store.commit(item, params)     }   })   return obj }
  export const mapActions = args => {   let obj = {}   args.forEach(item => {     obj[item] = function (payload) {       return this.$store.dispatch(item, payload)     }   })   return obj }
  function install(_Vue) {   Vue = _Vue       Vue.mixin({     beforeCreate() {              if (this.$options && this.$options.store) {                  this.$store = this.$options.store       } else if (this.$parent && this.$parent.$store) {                  this.$store = this.$parent.$store       }     },   }) }
  export default {   Store,   install, }
   | 
整个项目的源码地址:mini-vuex