在阅读Vuex源码之前,因为Vuex的api和使用功能略微复杂,默认以为实现起来相当复杂,望而生畏。然而通过深入学习源码,发现核心功能结合vue实现起来非常巧妙,也就核心几行代码,直呼内行。本文也就100左右行代码就能快速手写自己的Vuex代码!

前言

Vuex 是⼀个专为 Vue.js应⽤程序开发的状态管理模式。它采⽤集中式存储管理应⽤的所有组件的状态,并以相应的规则保证状态以⼀种可预测的⽅式发⽣变化。那么Vuex和单纯的全局对象有什么不同呢?

Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发⽣变 化,那么相应的组件也会相应地得到⾼效更新。

不能直接改变 store 中的状态。改变 store 中的状态的唯⼀途径就是显式地提交 (commit) mutation。这样使得我们可以⽅便地跟踪每⼀个状态的变化,从⽽让我们能够实现⼀些⼯具帮助我 们更好地了解我们的应⽤。

通过以上两点认知我们来快速实现自己的Vuex!

Vuex 初始化

为什么在vue实例化的时候要传入store去实例化呢?那是为了让vue所有的组件中可以通过 this.$store来获取该对象,即 this.$store æŒ‡å‘ store å®žä¾‹ã€‚

// Store å¾…实现const store = new Store({  state: {    count: 0,    num: 10})new Vue({  el: '#app',  store: store // æ­¤å¤„çš„ store ä¸º this.$options.store})

Vuex提供了install属性,通过Vue.use(Vuex)来注册。

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

Vue全局混⼊了⼀个 beforeCreated é’©â¼¦å‡½æ•°ï¼Œ options.store ä¿å­˜åœ¨æ‰€æœ‰ç»„ä»¶çš„ this.$store ä¸­ï¼Œè¿™ä¸ª options.store å°±æ˜¯æˆ‘们在实例化 Store å¯¹è±¡çš„实例。Store 对象的构造函数接收⼀个对象参数,它包含 actions ã€ getters ã€ state ã€ mutations ç­‰æ ¸â¼¼æ¦‚念,接下来我们一一实现。

Vuex state

其实 state 是 vue 实例中的 data ,通过 Store 内部创建Vue实例,将 state 存储到 data 里,然后改变 state 就是触发了 data 数据的改变从而实现了视图的更新。

// å®žä¾‹åŒ– Storeconst store = new Store({  state: {    count: 0,    num: 10
  }
})// Store å®žçްclass Store {  constructor({state = {}}) {    this.vm = new Vue({      data: {state} // state æ·»åŠ åˆ° data ä¸­
    })
  }  get state() {    return this.vm.state // å°† state代理到 vue å®žä¾‹ä¸­çš„ state
  }  set state(v) {    console.warn(`Use store.replaceState() to explicit replace store state.`)
  }
}

由上可知,store.state.count ç­‰ä»·äºŽ store.vm.state。不论是获取或者改变state里面的数据都是间接的触发了vue中data数据的变化,从而触发视图更新。

Vuex getters

知道state æ˜¯vue实例中的data,那么同理,getters 就是 vue中的计算属性 computed。

// å®žä¾‹åŒ– Storeconst store = new Store({  state: {    count: 0,    num: 10
  },  getters: {    total: state => {      return state.num + state.count
    }
  },
})// Store å®žçްclass Store {  constructor({state = {}, getters = {}}) {    this.getters = getters    // åˆ›å»ºæ¨¡æ‹Ÿ computed å¯¹è±¡
    const computed = {}    Object.keys(getters).forEach(key => {      const fn = getters[key]      // å…¥å‚ state å’Œ getters
      computed[key] = () => fn(this.state, this.getters)      // ä»£ç† getters åˆ° vm å®žä¾‹ä¸Š
      Object.defineProperty(this.getters, key, {        get: () => this.vm[key]
      })
    })    // èµ‹å€¼åˆ° vue ä¸­çš„ computed è®¡ç®—属性中
    this.vm = new Vue({      data: {
        state,
      },
      computed,
    })
  }  get state() {    return this.vm.state
  }  set state(v) {    console.warn(`Use store.replaceState() to explicit replace store state.`)
  }
}

使用 Object.defineProperty å°†getters上的所有属性都代理到了vm实例上的computed计算属性中,也就是 store.getters.count ç­‰ä»·äºŽ store.vm.count。

Vuex mutations

mutations等同于发布订阅模式,先在mutations中订阅事件,然后再commit发布事件。

// å®žä¾‹åŒ– Storeconst store = new Store({  state: {    count: 0,    num: 10
  },  mutations: {    INCREASE: (state, n) =>{
      state.count += n
    },    DECREASE: (state, n) =>{
      state.count -= n
    }
  }
})// Store å®žçްclass Store {  constructor({state = {},  mutations = {}, strict = false}) {    this.mutations = mutations    // ä¸¥æ ¼æ‘¸ç´¢åªèƒ½é€šè¿‡ commit æ”¹å˜ state
    this.strict && this.enableStrictMode()
  }

  commit(key, payload) {    // èŽ·å–äº‹ä»¶
    const fn = this.mutations[key]    // å¼€å§‹ commit
    this.committing = true
    // æ‰§è¡Œäº‹ä»¶ å¹¶ä¼ å‚
    fn(this.state, payload) 
    // ç»“束 commit  æ‰€ä»¥è¯´æ˜Ž commit åªèƒ½æ‰§è¡ŒåŒæ­¥äº‹ä»¶
    this.committing = false
  }

  enableStrictMode () {    // vm实例观察 state æ˜¯å¦ç”± commit è§¦å‘改变
    this.vm.$watch('state', () => {
      !this.committing 
      && 
      console.warn(`Do not mutate vuex store state outside mutation handlers.`)
    }, { deep: true, sync: true })
  }  
  get state() {    return this.vm.state
  }  set state(v) {    console.warn(`Use store.replaceState() to explicit replace store state.`)
  }
  
}

store.commit æ‰§è¡Œ mutations中的事件,通过发布订阅实现起来并不难。 Vuex中的严格模式,只能在commit的时候改变state数据,不然提示错误。

Vuex actions

mutations ç”¨äºŽåŒæ­¥æ›´æ–° state,而 actions åˆ™æ˜¯æäº¤ mutations,并可进行异步操作,从而间接更新 state。

// å®žä¾‹åŒ– Storeconst store = new Store({  actions: {
    getToal({dispatch, commit, state, getters}, n){      return new Promise(resolve => {
        setTimeout(() => {
          commit('DECREASE', n)
          resolve(getters.total)
        }, 1000)
      })
    }
  }
})// Store å®žçްclass Store {  constructor({actions = {}}) {    this.actions = actions
  }

  dispatch(key, payload) {    const fn = this.actions[key]    const {state, getters, commit, dispatch} = this
    // æ³¨æ„ this æŒ‡å‘
    const result = fn({state, getters, commit: commit.bind(this), dispatch: dispatch.bind(this)}, payload)    // è¿”回 promise
    return this.isPromise(result) ? result :  Promise.resolve(result)
  }  
  // åˆ¤æ–­æ˜¯å¦æ˜¯ promise
  isPromise (val) {    return val && typeof val.then === 'function'
  }

}

mutations å’Œ actions çš„实现大同小异,actions æ ¸å¿ƒåœ¨äºŽå¤„理异步逻辑,并返回一个 promise。

完整案例代码

这边把以上的代码统一归纳起来,可以根据这份完整代码来分析Vuex逻辑。


  
  
  Document
  
  
            
     

总结

通过上面的完整案例可知,Vuex核心代码也就100行左右,但是他巧妙的结合了vue的data和computeds属性,化繁为简,实现了复杂的功能,所以说vuex是不能脱离vue而独立运行的。
本文是结合官网源码提取核心思想手写自己的Vuex,而官网的Vuex,为了避免store结构臃肿,还实现了modules等功能,具体实现可以查看Vuex官网源码。