在浏览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 对象的构造函数接管⼀个对象参数,它蕴含 actionsgettersstatemutations 等核⼼概念,接下来咱们一一实现。

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

晓得statevue实例中的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'  }}

mutationsactions 的实现大同小异,actions 外围在于解决异步逻辑,并返回一个 promise

残缺案例代码

这边把以上的代码对立归纳起来,能够依据这份残缺代码来剖析Vuex逻辑。

<!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <title>Document</title>  <script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script></head><body>  <div id="app">    <child-a></child-a>    <child-b></child-b>  </div>  <script>    class Store {      constructor({state = {}, getters = {}, mutations = {}, actions = {}, strict = false}) {        this.strict = strict        this.getters = getters        this.mutations = mutations        this.actions = actions        this.committing = false        this.init(state, getters)      }      get state() {        return this.vm.state      }      set state(v) {        console.warn(`Use store.replaceState() to explicit replace store state.`)      }      init (state,  getters) {        const computed = {}        Object.keys(getters).forEach(key => {          const fn = getters[key]          computed[key] = () => fn(this.state, this.getters)          Object.defineProperty(this.getters, key, {            get: () => this.vm[key]          })        })        this.vm = new Vue({          data: {state},          computed,        })        this.strict && this.enableStrictMode()      }      commit(key, payload) {        const fn = this.mutations[key]        this.committing = true        fn(this.state, payload)        this.committing = false      }      dispatch(key, payload) {        const fn = this.actions[key]        const {state, getters, commit, dispatch} = this        const res = fn({state, getters, commit: commit.bind(this), dispatch: dispatch.bind(this)}, payload)        return this.isPromise(res) ? res :  Promise.resolve(res)      }      isPromise (val) {        return val && typeof val.then === 'function'      }      enableStrictMode () {        this.vm.$watch('state', () => {          !this.committing && console.warn(`Do not mutate vuex store state outside mutation handlers.`)        }, { deep: true, sync: true })      }    }        const install = function () {      Vue.mixin({        beforeCreate() {          if (this.$options.store) {            Vue.prototype.$store = this.$options.store          }        }      })    }    // 子组件 a    const childA = {      template: '<div><button @click="handleClick">click me</button> <button @click="handleIncrease">increase num</button> <button @click="handleDecrease">decrease num</button></div>',      methods: {        handleClick() {          this.$store.state.count += 1        },        handleIncrease() {          this.$store.commit('INCREASE', 5)        },        handleDecrease() {          this.$store.dispatch('getToal', 5).then(data => {            console.log('total', data)          })        }      }    }    // 子组件 b    const childB = {      template: '<div><h1>count: {{ count }}</h1><h1>total: {{ total }}</h1></div>',      mounted() {        // 严格模式下批改state的值将正告        // this.$store.state.count =  1      },      computed: {        count() {          return this.$store.state.count        },        total(){          return this.$store.getters.total        }      }    }    const store = new Store({      state: {        count: 0,        num: 10      },      getters: {        total: state => {          return state.num + state.count        }      },      mutations: {        INCREASE: (state, n) =>{          state.count += n        },        DECREASE: (state, n) =>{          state.count -= n        }      },      actions: {        getToal({dispatch, commit, state, getters}, n){          return new Promise(resolve => {            setTimeout(() => {              commit('DECREASE', n)              resolve(getters.total)            }, 1000)          })        }      }    })    Vue.use({install})    new Vue({      el: '#app',      components: {        'child-a': childA,        'child-b': childB      },      store: store    })  </script>  </body></html>

总结

通过下面的残缺案例可知,Vuex外围代码也就100行左右,然而他奇妙的联合了vuedatacomputeds属性,化繁为简,实现了简单的性能,所以说vuex是不能脱离vue而独立运行的。
本文是联合官网源码提取核心思想手写本人的Vuex,而官网的Vuex,为了防止store构造臃肿,还实现了modules等性能,具体实现能够查看Vuex官网源码。