跟我一塊兒從0到1編寫一個本身的Vuex

前言

在前端工程化開發的今天, vuexredux成爲了咱們項目中狀態管理的上上之選。關於如何使用它,相信這已經成爲前端開發者的必備技能之一了。今天,咱們來一塊兒嘗試進階一下,本身實現一個狀態管理器來管理咱們的項目,讓咱們能夠在之後的開發過程當中能夠更加迅捷的定位問題,能夠在遇到面試官提出(您好,能夠描述下 vuex的實現原理嗎?)相似問題的時候能夠更加從容的回答。

實際使用

相信大多數同窗在平常開發中會這樣使用vuex前端

// store.js
import Vue from "vue"
import Vuex from "vuex"
Vue.use(Vuex) 
export default new Vuex.Store({
  state: {
    text: "Hello Vuex"
  },
  getters: {},
  mutations: {},
  actions: {},
  modules: {}
)}

磨刀不誤砍柴工,簡單分析下vuex

咱們在引入vuex以後主要作了如下兩步操做vue

  1. Vue.use(Vuex)

'vue.use用法'
此處說明咱們的vuex必須得向外面暴露一個install方法,這個install方法能夠幫助咱們在vue原型上註冊咱們的功能。面試

  1. new Vuex.Store()

看到new了,顧名思義咱們的vuex不只須要暴露出install方法,一樣還須要暴露出一個store的類,上面掛載了咱們使用到的state、muations、actions、getters等參數以及commit、dispatch等方法vuex

開始搭建本身的vuex

實現vue.use

經過上面的簡要分析咱們能夠了解到咱們須要建立一個install函數和一個store的類,而後暴露出來redux

新建my-vuex.js前端工程化

// my-vuex.js
let Vue
const install = _Vue => {
// vue.use()執行的時候,會將vue做爲參數傳入進來,這裏咱們用一個變量接收 vue
  Vue = _Vue 
}
class Store {
    
}
export default {
  install,
  Store
}

vuex基本的結構咱們已經搭建好,接下來咱們來繼續完善install函數。install函數應該是一個實現掛載全局$store的過程。app

// my-vuex.js
let Vue
const install = _Vue => {
// vue.use()執行的時候,會將vue實例做爲參數傳入進來,這裏咱們用一個變量接收
  Vue = _Vue 
  // Vue.mixin幫助咱們全局混入$store
  Vue.mixin({
    beforeCreate(){
      // 這裏的this指的是vue實例
      const options = this.$options
      if(options.store){
        // 判斷當前組件內部是否認義了store,若是有則優先使用內部的store
        this.$store = typeof options.store === 'function' ? options.store() : options.store
      } else if(options.parent && options.parent.$store){
        // 組件內部沒有定義store,則從父組件下繼承$store方法
        this.$store = options.parent.$store
      }
    }
  })
}
class Store {
    
}
export default {
  install,
  Store
}

上面咱們已經經過vue.use$store實例注入到了vue上,下面咱們繼續完善store裏面的功能異步

實現state

咱們一般會在組件中使用this.$store.state來獲取數據,因此這裏咱們須要在Store類上定義獲取state時的方法async

my-vuex.js代碼以下模塊化

// 省略其他代碼 
class Store {
    constructor(options={}){
        this.options = options
    }
    get state(){
        return this.options.state
    }
}
export default {
  install,
  Store
}

測試一下

store.js

// store.js
import Vue from "vue"
import Vuex from "./my-vuex.js"
Vue.use(Vuex)
export default new Vuex.Store({
  state: {
    text: "Hello Vuex"
  },
  getters: {},
  mutations: {},
  actions: {},
  modules: {}
})

App.vue

<template>
  <div id="app">
    <h1>{{getState}}</h1>
  </div>
</template>
<script>
  export default{
    computed:{
      getState(){
        return this.$store.state.text 
      }
    }
  }
</script>

運行代碼後會發現展現出了預期的 Hello Vuex

可是在這裏有一個小問題,咱們都知道vue的數據是響應式的。若是咱們以下去操做:
// App.vue
<template>
  <div id="app">
      <h1>{{getState}}</h1>
  </div>
</template>
<script>
  export default{
    computed:{
      getState(){
        return this.$store.state.text 
      }
    },
    mounted(){
      setTimeout(() => {
        console.log('執行了')
        this.$store.state.text = 'haha'
      }, 1000)
    }
  }
</script>

代碼運行後會咱們發現頁面的數據並無變化,因此這裏咱們要將state改形成響應式的數據。這裏提供兩種方法

  1. 利用vue自身提供的data響應式機制
// my-vuex.js
// 省略多餘代碼
class Store {
    constructor(options={}){
        this.options = options
        this.vmData = new Vue({
          data: {
              state: options.state
          }
        });
    }
    get state(){
        return this.vmData._data.state
    }
}
  1. 利用vue2.6.0新增的Vue.observable()實現
// my-vuex.js
// 省略多餘代碼
class Store {
    constructor(options={}){
        this.options = options
        this.vmData = {
            state:Vue.observable(options.state || {})
        }
    }
    get state(){
        return this.vmData.state
    }
}

實現getters

my-vuex.js代碼以下

// my-vuex.js
// 省略多餘代碼
class Store {
    constructor(options={}){
        this.options = options
        this.vmData = {
            state:Vue.observable(options.state || {})
        }
      // 初始化getters
      this.getters = {}
      // 遍歷store上的getters
      Object.keys(options.getters).forEach(key=>{
        //爲getters裏全部的函數定義get時執行的操做
        Object.defineProperty(this.getters,key,{
          get:()=>{
            return options.getters[key](this.vmData.state)
          }
        })
      })
    }
    get state(){
        return this.vmData.state
    }
}

測試一下

store.js

import Vue from "vue"
import Vuex from "./my-vuex.js"
Vue.use(Vuex)
export default new Vuex.Store({
  state: {
    text: "Hello Vuex"
  },
  getters: {
    getText(state){
      return state.text
    }
  },
  mutations: {},
  actions: {},
  modules: {}
})

App.vue

<template>
  <div id="app">
      <h1>{{getState}}</h1>
  </div>
</template>
<script>
  export default{
    computed:{
      getState(){
        return this.$store.getters.getText
      }
    }
  }
</script>

實現mutation和commit方法

my-vuex.js代碼以下

// 省略多餘代碼
class Store {
    constructor(options={}){
        this.options = options
        this.vmData = {
            state:Vue.observable(options.state || {})
        }
      // 初始化getters
      this.getters = {}
      // 遍歷store上的getters
      Object.keys(options.getters).forEach(key=>{
        //爲getters裏全部的函數定義get時執行的操做
        Object.defineProperty(this.getters,key,{
          get:()=>{
            return options.getters[key](this.vmData.state)
          }
        })
      })
      // 初始化mutations
      this.mutations = {}
      // 遍歷mutations裏全部的函數
      Object.keys(options.mutations).forEach(key=>{
        // 拷貝賦值
        this.mutations[key] = payload=>{
          options.mutations[key](this.vmData.state,payload)
        }
      })
      // commit實際上就是執行mutations裏指定的函數
      this.commit = (type,param)=>{
        this.mutations[type](param)
      }
    }
    get state(){
        return this.vmData.state
    }
}

測試一下

store.js

import Vue from "vue"
import Vuex from "./my-vuex.js"
Vue.use(Vuex)
export default new Vuex.Store({
  state: {
    text: "Hello Vuex"
  },
  getters: {
    getText(state){
      return state.text
    }
  },
  mutations: {
    syncSetText(state,param){
      state.text = param
    }
  },
  actions: {},
  modules: {}
})

App.vue

<template>
  <div id="app">
      <h1>{{getState}}</h1>
  </div>
</template>
<script>
  export default{
    computed:{
      getState(){
        return this.$store.getters.getText
      }
    },
    mounted(){
      setTimeout(() => {
        console.log('執行了')
        this.$store.commit('syncSetText','同步更改數據')
      }, 1000)
    }
  }
</script>

實現action和dispatch方法

action與mutations原理相似,一樣dispatch實現方法與commit相似

my-vuex.js代碼以下

// 省略多餘代碼
class Store {
    constructor(options={}){
        this.options = options
        this.vmData = {
            state:Vue.observable(options.state || {})
        }
      // 初始化getters
      this.getters = {}
      // 遍歷store上的getters
      Object.keys(options.getters).forEach(key=>{
        //爲getters裏全部的函數定義get時執行的操做
        Object.defineProperty(this.getters,key,{
          get:()=>{
            return options.getters[key](this.vmData.state)
          }
        })
      })
      // 初始化mutations
      this.mutations = {}
      // 遍歷mutations裏全部的函數
      Object.keys(options.mutations).forEach(key=>{
        // 拷貝賦值
        this.mutations[key] = payload=>{
          options.mutations[key](this.vmData.state,payload)
        }
      })
      // commit實際上就是執行mutations裏指定的函數
      this.commit = (type,param)=>{
        this.mutations[type](param)
      }
      // 初始化actions
      this.actions = {} 
      Object.keys(options.actions).forEach(key => {
        this.actions[key] = payload => {
          options.actions[key](this, payload)
        }
      })
      this.dispatch = (type,param)=>{
        this.actions[type](param)
      }
    }
    get state(){
        return this.vmData.state
    }
}

測試一下

store.js

import Vue from "vue"
import Vuex from "./my-vuex.js"
Vue.use(Vuex)
export default new Vuex.Store({
  state: {
    text: "Hello Vuex"
  },
  getters: {
    getText(state){
      return state.text
    }
  },
  mutations: {
    syncSetText(state,param){
      state.text = param
    }
  },
  actions: {
    asyncSetText({commit},param){
      commit('syncSetText',param)
    }
  },
  modules: {}
})

App.vue

<template>
  <div id="app">
      <h1>{{getState}}</h1>
  </div>
</template>
<script>
  export default{
    computed:{
      getState(){
        return this.$store.getters.getText
      }
    },
    mounted(){
      setTimeout(() => {
        console.log('執行了')
        this.$store.dispatch('asyncSetText','異步更改數據')
      }, 1000)
    }
  }
</script>

精簡一下代碼

目前已經實現了vuex中基本的幾個功能,可是上面的代碼稍微現得有些冗餘,咱們來優化一下,主要從如下兩點入手

1.將出現屢次的Object.keys().forEach()封裝成公共的forEachValue函數

function forEachValue (obj, fn) {
  Object.keys(obj).forEach(key=>fn(obj[key], key));
}

2.把多個初始化從新賦值的部分封裝爲易讀的register函數

優化後的代碼以下

// my-vuex.js
// 省略多餘代碼
class Store {
  constructor(options={}){
      this.options = options
      this.vmData = {
          state:Vue.observable(options.state || {})
      }
      // 初始化getters
      this.getters = {}
      forEachValue(options.getters,(getterFn,getterName)=>{
        registerGetter(this,getterName,getterFn)
      }
      )
      // 初始化mutations
      this.mutations = {}
      forEachValue(options.mutations,(mutationFn,mutationName)=>{
          registerMutation(this,mutationName,mutationFn)
        }
      )
      // 初始化actions
      this.actions = {}
      forEachValue(options.actions,(actionFn,actionName)=>{
          registerAction(this,actionName,actionFn)
        }
      )
      // commit實際上就是執行mutations裏指定的函數
      this.commit = (type,param)=>{
        this.mutations[type](param)
      }
      this.dispatch = (type,param)=>{
        this.actions[type](param)
      }
  }
  get state(){
      return this.vmData.state
  }
}
// 註冊getter
function registerGetter(store,getterName,getterFn){
  Object.defineProperty(store.getters,getterName,{
    get:()=>{
      return getterFn.call(store,store.vmData.state)
    }
  })
}
// 註冊mutation
function registerMutation(store,mutationName,mutationFn){
  store.mutations[mutationName] = payload=>{
    mutationFn.call(store,store.vmData.state,payload)
  }
}
// 註冊action
function registerAction(store,actionName,actionFn){
  store.actions[actionName] = payload=>{
    actionFn.call(store,store,payload)
  }
}
// 封裝出公共的循環執行函數
function forEachValue (obj, fn) {
  Object.keys(obj).forEach(key=>fn(obj[key], key));
}
export default {
  install,
  Store
}

實現module模塊化

當咱們項目日益複雜化的時候勢必會引入module進行模塊化狀態管理,下面咱們來繼續實現module的功能

首先咱們一塊兒來看一下咱們通常怎樣使用module

store.js代碼以下

import Vue from "vue"
// import Vuex from "./my-vuex.js"
import Vuex from "vuex"
Vue.use(Vuex)
let moduleA = {
  state:{
    nameA:'我是模塊A'
  },
  mutations:{
    syncSetA(state,param){
      state.nameA = param
    }
  },
  actions:{
    asyncSetState({commit},param){
      setTimeout(()=>{
        commit('syncSetA',param)
      },1000)
    }
  },
  getters:{
    getA(state){
      return state.nameA
    }
  }
}
let moduleB = {
  state:{
    nameB:'我是模塊B'
  },
  mutations:{
    syncSetB(state,param){
      state.nameB = param
    }
  },
  actions:{
    asyncSetState({commit},param){
      setTimeout(()=>{
        commit('syncSetB',param)
      },1000)
    }
  },
  getters:{
    getB(state){
      return state.nameB
    }
  }
}
export default new Vuex.Store({
  modules:{
    moduleA,moduleB
  },
  state: {
    text: "Hello Vuex"
  },
  getters: {
    getText(state){
      return state.text
    }
  },
  mutations: {
    syncSetText(state,param){
      state.text = param
    }
  },
  actions: {
    asyncSetText({commit},param){
      commit('syncSetText',param)
    }
  }
})

App.vue代碼以下

<template>
  <div id="app">
      <h1>{{getState}}</h1>
      A<h2>{{stateA}}</h2>
      B<h2>{{stateB}}</h2>
  </div>
</template>
<script>
  export default{
    computed:{
      getState(){
        return this.$store.getters.getText
      },
      stateA(){
        return this.$store.state.moduleA.nameA
      },
      stateB(){
        return this.$store.state.moduleB.nameB
      }
    },
    mounted(){
      setTimeout(() => {
        this.$store.dispatch('asyncSetState','異步更改數據')
      }, 1000)
    }
  }
</script>

在不啓用nameSpace的狀況下,咱們發現咱們獲取模塊內的state使用this.$store.state.moduleB.nameA的方式獲取。而觸發模塊內的mutations或者action則是與之前同樣,只不過如果兩個不一樣的模塊有重名的mutation或者action,則須要所有都執行。下面運用兩個步驟進行模塊化實現

1. 格式化modules傳來的數據

若是咱們的store.js是這樣的

export default new Vuex.Store({
  modules:{
    moduleA,moduleB
  },
  state: {},
  getters: {},
  mutations: {},
  actions: {}
})

咱們能夠格式化成下面這種格式,造成一個模塊狀態樹

const newModule = {
    // 根模塊store
    _rootModule:store,
    // 子模塊
    _children:{
        moduleA:{
          _rootModule:moduleA,
          _children:{},
          state:moduleA.state
        },
        moduleB:{
          _rootModule:moduleB,
          _children:{},
          state:moduleB.state
        }
    },
    // 根模塊狀態
    state:store.state
}

爲此咱們須要新增一個moduleCollection類來收集store.js中的數據,而後格式化成狀態樹

my-vuex.js代碼以下

// my-vuex.js
let Vue
const install = _Vue => {
// 省略部分代碼
}
class Store {
  constructor(options={}){
      // 省略部分代碼
      // 格式化數據,生成狀態樹
      this._modules = new ModuleCollection(options)
  }
}
class moduleCollection{
  constructor(rootModule){
    this.register([],rootModule)
  }
  register(path,rootModule){
    const newModule = {
      _rootModule:rootModule, // 根模塊 
      _children:{}, // 子模塊
      state:rootModule.state // 根模塊狀態
    }
    // path長度爲0,說明是根元素進行初始化數據
    if(path.length === 0){
      this.root = newModule 
    }else{
      //利用reduce能夠快速的將扁平化數據轉換成樹狀數據
      const parent = path.slice(0,-1).reduce((module,key)=>{
        return module._children(key)
      },this.root)
      parent._children[path[path.length - 1]] = newModule
    }
    // 若是含有modules,則須要循環註冊內部模塊
    if(rootModule.modules){
      forEachValue(rootModule.modules,(rootChildModule,key)=>{
        this.register(path.concat(key),rootChildModule)
      })
    }
}}

2. 安裝狀態樹

store.js中的數據已經被咱們遞歸組裝成了狀態樹,接下來須要將狀態樹安裝進Store類中
這裏主要作了兩個改動

  1. 新增installModule函數,installModule主要幫助咱們將格式化好的狀態樹註冊到Store類中
  2. 從新改造了註冊函數(registerMutation、registerGetter等)以及觸發函數(commit、dispatch)。

my-vuex.js代碼以下

// my-vuex.js
// 省略部分代碼
class Store {
  constructor(options={}){
      this.options = options
      // 初始化getters
      this.getters = {}
      // 初始化mutations
      this.mutations = {}
      // 初始化actions
      this.actions = {}
      // 初始化數據,生成狀態樹
      this._modules = new moduleCollection(options)
      this.commit = (type,param)=>{
        this.mutations[type].forEach(fn=>fn(param))
      }
      this.dispatch = (type,param)=>{
        this.actions[type].forEach(fn=>fn(param))
      }
      const state = options.state;
      const path = []; // 初始路徑給根路徑爲空
      installModule(this, state, path, this._modules.root);
      this.vmData = {
        state:Vue.observable(options.state || {})
      }
  }
  get state(){
      return this.vmData.state
  }
}
class moduleCollection{
    // 省略部分代碼
}
// 遞歸狀態樹,掛載getters,actions,mutations
function installModule(store, rootState, path, rootModule) {
  // 這兒將模塊中的state循環出來設置到根state中去,以便咱們經過this.$store.state.moduleA來訪問數據
  if (path.length > 0) {
    const parent = path.slice(0,-1).reduce((state,key)=>{
      return state[key]
    },rootState)
    Vue.set(parent, path[path.length - 1], rootModule.state)
  }
  // 循環註冊包含模塊內的全部getters
  let getters = rootModule._rootModule.getters
  if (getters) {
    forEachValue(getters, (getterFn, getterName) => {
      registerGetter(store, getterName, getterFn, rootModule);
    });
  }
  // 循環註冊包含模塊內的全部mutations
  let mutations = rootModule._rootModule.mutations
  if (mutations) {
    forEachValue(mutations, (mutationFn, mutationName) => {
      registerMutation(store, mutationName, mutationFn, rootModule)
    });
  }
  // 循環註冊包含模塊內的全部actions
  let actions = rootModule._rootModule.actions
  if (actions) {
    forEachValue(actions, (actionFn, actionName) => {
      registerAction(store, actionName, actionFn, rootModule);
    });
  }
  // 若是模塊嵌套模塊,則須要遞歸安裝
  forEachValue(rootModule._children, (child, key) => {
      installModule(store, rootState, path.concat(key), child)
  })
}
// 這兒的getters中的state是各自模塊中的state
function registerGetter(store,getterName,getterFn,currentModule){
  Object.defineProperty(store.getters,getterName,{
    get:()=>{
      return getterFn.call(store,currentModule.state)
    }
  })
}
// 因爲各個模塊mutation存在重複狀況,所以這裏使用發佈-訂閱模式進行註冊
function registerMutation(store,mutationName,mutationFn,currentModule){
  let mutationArr = store.mutations[mutationName] || (store.mutations[mutationName] = []);
  mutationArr.push((payload)=>{
    mutationFn.call(store,currentModule.state,payload)
  })
}
function registerAction(store,actionName,actionFn){
  let actionArr = store.actions[actionName] || (store.actions[actionName] = []);
  actionArr.push((payload)=>{
    actionFn.call(store,store,payload)
  })
}
// 省略其他代碼

至此,咱們已經實現了vuex的基本功能,固然其餘相似於nameSpace、plugins,store.subscribe的功能這裏並無展開,小夥伴們能夠自行擴展。這裏建議小夥伴們先要理清楚思路。從vuex是什麼,要實現那些功能?怎樣能夠更好的實現?若是思路通了,相信你們能夠寫出更好的vuex

附贈vuex中輔助函數mapState,mapGetters,mapMutations,mapActions的實現

輔助函數的實現原理較爲簡單,你們自行嘗試

const mapState = stateList => {
  return stateList.reduce((prev,stateName)=>{
    prev[stateName] =function(){
      return this.$store.state[stateName]
    }
    return prev
  },{})
}
const mapGetters = gettersList => {
  return gettersList.reduce((prev,gettersName)=>{
    prev[gettersName] =function(){
      return this.$store.getters[gettersName]
    }
    return prev
  },{})
}
const mapMutations = mutationsList => {
  return mutationsList.reduce((prev,mutationsName)=>{
    prev[mutationsName] =function(payload){
      return this.$store.commit(mutationsName,payload)
    }
    return prev
  },{})
}
const mapActions = actionsList => {
  return actionsList.reduce((prev,actionsName)=>{
    prev[actionsName] =function(payload){
      return this.$store.dispatch(actionsName,payload)
    }
    return prev
  },{})
}

本文完整代碼

// my-vuex.js
let Vue
const install = _Vue => {
  // vue.use()執行的時候,會將vue實例做爲參數傳入進來,這裏咱們用一個變量接收
    Vue = _Vue 
    // Vue.mixin幫助咱們全局混入$store
    Vue.mixin({
      beforeCreate(){
        // 這裏的this指的是vue實例
        const options = this.$options
        if(options.store){
          // 判斷當前組件內部是否認義了store,若是有則優先使用內部的store
          this.$store = typeof options.store === 'function' ? options.store() : options.store
        } else if(options.parent && options.parent.$store){
          // 組件內部沒有定義store,則從父組件下繼承$store方法
          this.$store = options.parent.$store
        }
      }
    })
}
class Store {
  constructor(options={}){
      this.options = options
      // 初始化getters
      this.getters = {}
      // 初始化mutations
      this.mutations = {}
      // 初始化actions
      this.actions = {}
      // 初始化數據,生成狀態樹
      this._modules = new moduleCollection(options)
      // commit實際上就是執行mutations裏指定的函數
      this.commit = (type,param)=>{
        this.mutations[type].forEach(fn=>fn(param))
      }
      this.dispatch = (type,param)=>{
        this.actions[type].forEach(fn=>fn(param))
      }
      const state = options.state;
      const path = []; // 初始路徑給根路徑爲空
      installModule(this, state, path, this._modules.root);
      this.vmData = {
        state:Vue.observable(options.state || {})
      }
  }
  get state(){
      return this.vmData.state
  }
}
// 格式化狀態樹
class moduleCollection{
  constructor(rootModule){
    this.register([],rootModule)
  }
  register(path,rootModule){
    const newModule = {
      _rootModule:rootModule, // 根模塊 
      _children:{}, // 子模塊
      state:rootModule.state // 根模塊狀態
    }
    // path長度爲0,說明是根元素進行初始化數據
    if(path.length === 0){
      this.root = newModule 
    }else{
      //利用reduce能夠快速的將扁平化數據轉換成樹狀數據
      const parent = path.slice(0,-1).reduce((module,key)=>{
        return module._children[key]
      },this.root)
      parent._children[path[path.length - 1]] = newModule
    }
    // 若是含有modules,則須要循環註冊內部模塊
    if(rootModule.modules){
      forEachValue(rootModule.modules,(rootChildModule,key)=>{
        this.register(path.concat(key),rootChildModule)
      })
    }
}}
// 遞歸狀態樹,掛載getters,actions,mutations
function installModule(store, rootState, path, rootModule) {
  // 這兒將模塊中的state循環出來設置到根state中去,以便咱們經過this.$store.state.moduleA來訪問數據
  if (path.length > 0) {
    const parent = path.slice(0,-1).reduce((state,key)=>{
      return state[key]
    },rootState)
    Vue.set(parent, path[path.length - 1], rootModule.state)
  }
  // 循環註冊包含模塊內的全部getters
  let getters = rootModule._rootModule.getters
  if (getters) {
    forEachValue(getters, (getterFn, getterName) => {
      registerGetter(store, getterName, getterFn, rootModule);
    });
  }
  // 循環註冊包含模塊內的全部mutations
  let mutations = rootModule._rootModule.mutations
  if (mutations) {
    forEachValue(mutations, (mutationFn, mutationName) => {
      registerMutation(store, mutationName, mutationFn, rootModule)
    });
  }
  // 循環註冊包含模塊內的全部actions
  let actions = rootModule._rootModule.actions
  if (actions) {
    forEachValue(actions, (actionFn, actionName) => {
      registerAction(store, actionName, actionFn, rootModule);
    });
  }
  // 若是模塊嵌套模塊,則須要遞歸安裝
  forEachValue(rootModule._children, (child, key) => {
      installModule(store, rootState, path.concat(key), child)
  })
}
// 這兒的getters中的state是各自模塊中的state
function registerGetter(store,getterName,getterFn,currentModule){
  Object.defineProperty(store.getters,getterName,{
    get:()=>{
      return getterFn.call(store,currentModule.state)
    }
  })
}
// 因爲各個模塊mutation存在重複狀況,所以這裏使用發佈-訂閱模式進行註冊
function registerMutation(store,mutationName,mutationFn,currentModule){
  let mutationArr = store.mutations[mutationName] || (store.mutations[mutationName] = []);
  mutationArr.push((payload)=>{
    mutationFn.call(store,currentModule.state,payload)
  })
}
function registerAction(store,actionName,actionFn){
  let actionArr = store.actions[actionName] || (store.actions[actionName] = []);
  actionArr.push((payload)=>{
    actionFn.call(store,store,payload)
  })
}
function forEachValue (obj, fn) {
  Object.keys(obj).forEach(key=>fn(obj[key], key));
}
// 輔助函數
export const mapState = stateList => {
  return stateList.reduce((prev,stateName)=>{
    prev[stateName] =function(){
      return this.$store.state[stateName]
    }
    return prev
  },{})
}
export const mapGetters = gettersList => {
  return gettersList.reduce((prev,gettersName)=>{
    prev[gettersName] =function(){
      return this.$store.getters[gettersName]
    }
    return prev
  },{})
}
export const mapMutations = mutationsList => {
  return mutationsList.reduce((prev,mutationsName)=>{
    prev[mutationsName] =function(payload){
      return this.$store.commit(mutationsName,payload)
    }
    return prev
  },{})
}
export const mapActions = actionsList => {
  return actionsList.reduce((prev,actionsName)=>{
    prev[actionsName] =function(payload){
      return this.$store.dispatch(actionsName,payload)
    }
    return prev
  },{})
}
export default {
  install,
  Store,
}

相關文章
相關標籤/搜索