Vuex源码解析和实现

166 阅读7分钟

一、Vuex介绍

Vuex是一个专为Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。 image.png

每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)Vuex 和单纯的全局对象有以下两点不同:

  1. Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
  2. 不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地 提交commit(mutation)。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

二、Vuex使用

1. Vuex在Vue项目中的接入步骤

  • 第一步:引入Vuex插件库,使用Vue.use(Vuex)进行挂在;
  • 第二步:使用new Vuex.Store(options)创建一个Store实例对象,用来作为VueApp的全局状态管理器;
  • 第三步:在new Vue(options)创建一个VueApp的根组件时,将第二步中的Store实例以store的属性传入到这个options配置中,即可在这个VueAPP应用的所有组件中通过this.$store属性拿到这个Store实例对象的全局状态管理器。

2. Vuex的配置说明和使用方式

VuexStore实例化参数options

  1. status:单一状态树,用一个对象就包含了全部的应用层级状态
  2. getter:计算属性,函数对象类型数据,从state中派生出一些状态, 监听status的状态自动执行对应函数,返回最新属性值
  3. mutations:同步方式更新status属性值的方法,也是唯一改变status的合法方式,不允许直接修改status的数据
  4. actions:带有异步操作的方式更新status属性值的方法,一部操作执行后内部在调用mutations的方法修改status的数据状态
  5. modules: 用来按照指定的模块数据结构来构建全局的状态管理器,在大型项目中,当全局数据状态比较多时,可以更方便清晰的管理所有状态,内部对应的key为每个模块名称,值为对应子模块的配置参数。
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const persits = (store) => {
  store.subscribe((mutations, state) => {
    console.log('persits plugins had runed!', mutations, state)
  })
}
export default new Vuex.Store({
  plugins: [persits],
  state: {
    firstName: '张',
    lastName: '三丰',
    age: 98
  },
  getters: {
    fullName (state) {
      return state.firstName + state.lastName
    }
  },
  mutations: {
    changeName (state, payload) {
      state.lastName = payload
    },
    syncAddAge (state, payload) {
      console.log('root mutations: syncAddAge runed!')
      state.age += payload
    },
    muintAge (state, payload) {
      state.age -= payload
    }
  },
  actions: {
    asyncMuintAge ({ commit }, payload) {
      console.log('root actions: asyncMuintAge runed!')
      setTimeout(() => {
        commit('muintAge', payload)
      }, 1000)
    }
  },
  // 所有的getters都是直接挂载在全局的store.getters上面的,
  // 如果父模块已经有这个名称的getters,Vuex会报错提示,读取的是用父模块的getters,
  // 如果多个模块有相同名称的getters,Vuex会报错提示,从第一个模块开始深度遍历读取
  modules: {
    moduleA: {
      state: {
        jobType: '开发',
        desc: '写代码的码农~~~',
        age: 10
      },
      getters: {
        // 模块内部的 getter 函数参数:
        // 第一个参数为该模块的局部状态state,
        // 第二个参数为store的全部getters对象,即所有模块的getters的集合,
        // 第三个参数为根节点的状态对象
        develop (state, getters, rootState) {
          console.log('模块内部getters的第二个参数:', getters)
          console.log(rootState.firstName + rootState.lastName)
          return '第一个一级模块的getter数据:' + state.jobType + ': ' + state.desc
        },
        webA (state) {
          return '第一个一级模块的getter数据:' + state.jobType + ' ' + state.desc
        }
      },
      mutations: {
        // 模块内部的 mutations 函数参数:
        // 第一个参数为该模块的局部状态state
        // 第二个参数为调用时传入的载荷payload
        syncAddAge (state, payload) {
          console.log('moduleA mutations: syncAddAge runed!')
          state.age += payload
        },
        renameJob (state, payload) {
          // 这里的 `state` 对象是模块的局部状态
          state.jobType = payload
        }
      },
      actions: {
        // 模块内部的 mutations 函数参数:
        // 第一个参数为该模块的store对象实例,包含了 该模块的局部状态state、Store的commit方法、根节点的状态对象rootState
        // 第二个参数为调用时传入的载荷payload
        aynscRenameJob ({ state, commit, rootState }, payload) {
          // 模块内部的 action,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState:
          if ((Object.keys(state).length + rootState.age) % 2 === 1) {
            commit('renameJob', payload)
          }
        }
      },
      modules: {
        subModule1: {
          state: {
            jobName: '前端',
            salary: '18K'
          },
          getters: {
            web (state) {
              return '二级模块的getter数据:' + state.jobName + ' ' + state.salary
            }
          },
          actions: {
            asyncMuintAge ({ commit }, payload) {
              console.log('moduleA.subModule1 actions: asyncMuintAge runed!', payload)
            }
          }
        }
      }
    },
    moduleB: {
      state: {
        jobName: '产品',
        desc: '设计思考产品功能的~~~'
      },
      getters: {
        production (state) {
          return state.jobName + ' ' + state.desc
        },
        developB (state) {
          return '第二个一级模块的getter数据:' + state.jobName + ' ' + state.desc
        },
        webB (state) {
          return '第二个一级模块的getter数据:' + state.jobName + ' ' + state.desc
        }
      }
    }
  }
})

从上面的使用看,Vuex的模块化使用有一下特点:

  • 所有的getter都是直接挂载在全局的store.getters上面的,如果父模块已经有这个名称的getterVuex会报错提示,读取的是用父模块的getter,如果多个模块有相同名称的getterVuex会报错提示,显示的数据是从所有模块树中深度遍历读取(从上往下,一个一个的深度查找).可以通过给模块添加namespaced: true为命名空间来避免这个读取问题
  • store中的所有 getter,第一个参数为模块局部的状态对象,第二个参数为store的所有getters对象,根节点状态会作为第三个参数暴露出来;
  • store中的所有 mutation,接收的第一个参数是模块的局部状态对象;
  • 对于模块内部的 action,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState;
  • 多个模块之间重名的mutation,执行this.$store.commit('mutationName', payload),所有模块中名为mutationNamemutation都会被执行。可以通过给模块添加namespaced: true为命名空间来避免这个执行问题
  • 多个模块之间重名的actions,执行this.$store.dispatch('actionsName', payload),所有模块中名为actionsNameactions都会被执行。可以通过给模块添加namespaced: true为命名空间来避免这个执行问题
  • vuex创建实例的时候,可以给该store传入插件,可以监听在提交commit修改state的时候触发插件执行。每一个插件就是一个函数,接受参数为当前的store,该store有一个subscribe的订阅事件的方法,接受参数为storemutationsstate.

3. Vuex模块化命名空间

默认情况下,模块内部的 actionmutationgetter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutationaction 作出响应。

如果希望模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getteractionmutation 都会自动根据模块注册的路径调整命名。例如:

const store = new Vuex.Store({
  modules: {
    account: {
      // 一级子模块启用命名空间,访问 getters mutations actions就需要带上对应的路径
      namespaced: true,
      state: () => ({ ... }), // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响
      getters: {
        isAdmin () { ... } // -> getters['account/isAdmin']
      },
      mutations: {
        login () { ... } // -> commit('account/login')
      },
      actions: {
        login () { ... } // -> dispatch('account/login')
      },
      // 嵌套模块
      modules: {
        // 继承父模块的命名空间
        myPage: {
          state: () => ({ ... }),
          getters: {
            profile () { ... } // -> getters['account/profile']
          }
        },
        //  继承父模块的命名空间的同时,进一步嵌套命名空间
        posts: {
          namespaced: true,
          state: () => ({ ... }),
          getters: {
            popular () { ... } // -> getters['account/posts/popular']
          }
        }
      }
    }
  }

启用了命名空间的 getteraction 会收到局部化的 getterdispatch 和 commit。换言之,你在使用模块内容时不需要在同一模块内额外添加空间名前缀。更改 namespaced 属性后不需要修改模块内的代码。

a. 在带命名空间的模块内访问全局内容

如果你希望使用全局 stategetterrootState 和 rootGetters 会作为第三和第四参数传入 getter,也会通过 context 对象的属性传入 action

若需要在全局命名空间内分发 action 或提交 mutation,将 { root: true } 作为第三参数传给 dispatch 或 commit 即可。

modules: {
  foo: {
    namespaced: true,
    getters: {
      // 在这个模块的 getter 中,`getters` 被局部化了
      // 你可以使用 getter 的第四个参数来调用 `rootGetters`
      someGetter (state, getters, rootState, rootGetters) {
        getters.someOtherGetter // -> 'foo/someOtherGetter'
        rootGetters.someOtherGetter // -> 'someOtherGetter'
      },
      someOtherGetter: state => { ... }
    },
    actions: {
      // 在这个模块中, dispatch 和 commit 也被局部化了
      // 他们可以接受 `root` 属性以访问根 dispatch 或 commit
      someAction ({ dispatch, commit, getters, rootGetters }, payload) {
        getters.someGetter // -> 'foo/someGetter'
        rootGetters.someGetter // -> 'someGetter'
        dispatch('someOtherAction', payload) // -> 'foo/someOtherAction'
        dispatch('someOtherAction', payload, { root: true }) // -> 'someOtherAction'
        commit('someMutation', payload) // -> 'foo/someMutation'
        commit('someMutation', payload, { root: true }) // -> 'someMutation'
      },
      someOtherAction (ctx, payload) { ... }
    }
  }
}

b. 在带命名空间的模块注册全局action

若需要在带命名空间的模块注册全局 action,你可添加 root: true,并将这个 action 的定义放在函数 handler 中。例如:

{
  actions: {
    someOtherAction ({dispatch}) {
      dispatch('someAction')
    }
  },
  modules: {
    foo: {
      namespaced: true,
      actions: {
        someAction: {
          root: true,
          handler (namespacedContext, payload) { ... } // -> 'someAction'
        }
      }
    }
  }
}

c. 带命名空间的绑定函数

当使用 mapStatemapGettersmapActions 和 mapMutations 这些函数来绑定带命名空间的模块时,写起来可能比较繁琐:

computed: {
  ...mapState({
    a: state => state.some.nested.module.a,
    b: state => state.some.nested.module.b
  })
},
methods: {
  ...mapActions([    'some/nested/module/foo', // -> this['some/nested/module/foo']()
    'some/nested/module/bar' // -> this['some/nested/module/bar']()
  ])
}

对于这种情况,你可以将模块的空间名称字符串作为第一个参数传递给上述函数,这样所有绑定都会自动将该模块作为上下文。于是上面的例子可以简化为:

computed: {
  ...mapState('some/nested/module', {
    a: state => state.a,
    b: state => state.b
  })
},
methods: {
  ...mapActions('some/nested/module', [
    'foo', // -> this.foo()
    'bar' // -> this.bar()
  ])
}

而且,你可以通过使用 createNamespacedHelpers 创建基于某个命名空间辅助函数。它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数:

import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')
export default {
  computed: {
    // 在 `some/nested/module` 中查找
    ...mapState({
      a: state => state.a,
      b: state => state.b
    })
  },
  methods: {
    // 在 `some/nested/module` 中查找
    ...mapActions([
      'foo',
      'bar'
    ])
  }
}

d. 模块动态注册

store 创建之后,你可以使用 store.registerModule 方法注册模块:

import Vuex from 'vuex'
const store = new Vuex.Store({ /* 选项 */ })
// 注册模块 `myModule`
store.registerModule('myModule', {
  // ...
})
// 注册嵌套模块 `nested/myModule`
store.registerModule(['nested', 'myModule'], {
  // ...
})

之后就可以通过 store.state.myModule 和 store.state.nested.myModule 访问模块的状态。

你也可以使用 store.unregisterModule(moduleName) 来动态卸载模块。注意,不能使用此方法卸载静态模块(即创建 store 时声明的模块),可以通过 store.hasModule(moduleName) 方法检查该模块是否已经被注册到 store

e. 保留state

在注册一个新 module 时,你很有可能想保留过去的 state,例如从一个服务端渲染的应用保留 state。你可以通过 preserveState 选项将其归档:store.registerModule('a', module, { preserveState: true })

当你设置 preserveState: true 时,该模块会被注册,actionmutationgetter 会被添加到 store 中,但是 state 不会。这里假设 storestate 已经包含了这个 modulestate 并且你不希望将其覆写。

f. 模块重用

有时我们可能需要创建一个模块的多个实例,例如:

如果我们使用一个纯对象来声明模块的状态,那么这个状态对象会通过引用被共享,导致状态对象被修改时 store 或模块间数据互相污染的问题。

实际上这和 Vue 组件内的 data 是同样的问题。因此解决办法也是相同的——使用一个函数来声明模块状态(仅 2.3.0+ 支持):

const MyReusableModule = {
  state: () => ({
    foo: 'bar'
  }),
  // mutation, action 和 getter 等等...
}

三、Vuex重点解析

1. Vuex数据的混入

Vue在使用Vuex之前,要执行Vue.use(Vuex)来载入Vuex,然后再使用new Vuex.Store(options)创建Vuex的数据实例对象,并在创建Vue的根组件的时候,以store参数配置到Vue根组件中。

在执行Vue.use(Vuex)的时候,会自动调用Vuex的install方法,,传入的参数是Vue的构造函数,在这个方法中使用Vuemixin方法,给Vue组件的beforeCreate钩子函数全局混入给所有Vue实例挂载相同的store实例对象。主要逻辑代码如下:

function install (_Vue) {
  // Vue.use(Vuex)的时候会执行Vuex的install方法
  Vue = _Vue // 在Vuex中全局保存Vue构造函数,在其他的地方会有使用到
  Vue.mixin({
    // Vuex混入一个createBefore函数,每次new Vue实例的时候都会执行这个钩子函数,
    // 然后在执行组件自定义的createBefore函数
    beforeCreate () {
      console.log('混入函数到所有vue实例中!,在这里将获取每个组件实例,并将$store注册到实例上去')
      console.log(this.$options) // 每个vue组件实例创建时的配置对象this.$options
      if (this.$options && this.$options.store) {
        // 组件的配置对象上存在store,表示这个是根组件实例对象
        this.$store = this.$options.store
      } else {
        // 其他子组建,自身的$options上是没有store对象的,可以从父组件上取之前注册上去的$store
        this.$store = this.$parent && this.$parent.$store
      }
    }
  })
}

这里的难点在于怎样把全局唯一的Store实例对象注册到每一个Vue组件实例的$store属性上,从上面代码的逻辑可以看出,在Vue初始化创建APP应用的根组件时,会将全局的Store实例对象传入,所以在根组件的$options上就可以拿到这个Store实例对象,同时将Store实例对象设置到根组件的$store上。其他子组件想要取到这个Store实例对象,就可以从它的父组件对象上面取到这个Store实例对象,并设置到每个子组件实例的$store上,这样就可以在应用的任何组件实例上通过$store参数取到这个Store实例。

2. Vuex状态数据的响应式

Vuex的status数据是响应式的,直接将options.store.state赋值给Store实例的state肯定不会是响应式的,这样的话直接修改store的state数据时,页面是不是响应式的更新的。为了结果这个问题,可以采用以下的方式:

class Store {
  constructor (options) {
    // 在注册Store创建实例对象的时候执行的函数,传入的就是store的配置对象
    // 将配置对象添加到Vuex的实例上之后,就可以在组件中通过this.$store对象访问
    // this.state = options.state // 这样直接将配置对象options.state赋值给this.state,数据不是响应式的
    // 需要借助Vue的响应式原理,通过 new Vue() 方式将this.store变成响应式的
    this.s = new Vue({
      data () {
        return { state: options.state }
      }
    })
  }

  // 为了能够通过 this.state 直接读取到中间变量 this.s 上的响应式数据,需要在这里使用类的get查询器做一个代理
  get state () {
    return this.s.state
  }
}

响应式数据可以直接依赖于Vue的响应式系统,如上代码所示,将配置的options.state状态数据对象直接设置给一个Vue实例的data属性即可,然后要能直接从Store实例的state上读取到这个,可以给这个Store实例添加一个查询器参数,读取这个参数时,返回这个Vue实例的响应式数据。

3. 实现简易的Vuex功能类

根据Vuex的使用方式,实现如下的一个简易版本的Vuex,能够获取响应式的state状态数据、配置getters的计算属性、commit同步提交数据变更、dispatch异步提交数据变更的基本功能操作。

class Store {
  constructor (options) {
    // 在注册Store创建实例对象的时候执行的函数,传入的就是store的配置对象
    // 将配置对象添加到Vuex的实例上之后,就可以在组件中通过this.$store对象访问
    // this.state = options.state // 这样直接将配置对象options.state赋值给this.state,数据不是响应式的
    // 需要借助Vue的响应式原理,通过 new Vue() 方式将this.store变成响应式的
    this.state = new Vue({
      data () {
        return options.state
      }
    })
    // 给Store实例对象$store添加getters对象属性
    this.getters = {}
    Object.entries(options.getters).forEach(([getterName, fn]) => {
      // this.getters[getterName] = fn(this.state) // 这里的getters是计算属性,每次读取都需要根据state的数据动态计算。直接赋值是不行的
      Object.defineProperty(this.getters, getterName, {
        get: () => {
          return fn(this.state)
        }
      })
    })
    // 给Store实例对象$store添加mutations对象属性
    this.mutations = {}
    Object.entries(options.mutations).forEach(([mutationName, fn]) => {
      this.mutations[mutationName] = (payload) => {
        fn(this.state, payload)
      }
    })
    // 给Store实例对象$store添加actions对象属性
    this.actions = {}
    Object.entries(options.actions).forEach(([actionName, fn]) => {
      this.actions[actionName] = (payload) => {
        fn(this, payload)
      }
    })
  }
  // 给Store实例对象$store添加commit方法
  commit = (mutationName, payload) => {
    this.mutations[mutationName](payload)
  }
  // 给Store实例对象$store添加dispatch方法
  dispatch = (actionName, payload) => {
    this.actions[actionName](payload)
  }
}

4. Vuex的模块化实现

Vuex首先将用户传入的options对象重新组装成下面的格式:

let root = {         // 根的数据节点,然后递归生产下面的树状结构
   _row: options,    // 当前节点的options
   _children: {      // 当前节点的子模块
     moduleA: {
       _row: {},
       _children: {}
       tate: options.state
     },
     moduleB: {}
   },
   state: options.state // 当前节点的state数据
}

vuex的模块化的实现逻辑代码: 具体实现如下:

  • 采用ModuleCollection类递归将用户传入的options构造出树形的结构
  • 调用installModule函数将模块挂在到store下面
let Vue
class ModuleCollection {
  constructor (options) {
    this.register([], options) // 递归将模块注册为对应的树结构
  }
  register (path, rootModule) {
    const module = { // 将模块格式化
      _rowModule: rootModule,
      _children: {},
      state: rootModule.state
    }
    if (path.length === 0) {
      // 根模块
      this.root = module
    } else {
      // 递归后续进来
      // 直接这样写是有问题的, 多级的字模块会被格式化为一级了, moduleA的子模块变成了第三个模块
      // this.root._children[path[path.length - 1]] = module
      const parent = path.slice(0, -1).reduce((root, current) => {
        return root._children[current]
      }, this.root)
      parent._children[path[path.length - 1]] = module
    }
    // 当前模块是否有modules
    if (rootModule.modules) {
      Object.entries(rootModule.modules).forEach(([moduleName, module]) => {
        this.register(path.concat(moduleName), module)
      })
    }
  }
}
function installModule (store, rootState, path, rootModule) {
  if (path.length) {
    // 是子模块,需要找到父模块,并将自己的状态发放上去,实现查找挂载数据格式
    // rootModule = rootModule._children[path[path.length-1]]
    const parent = path.slice(0, -1).reduce((root, current) => {
      return root[current]
    }, rootState)
    // 这样设置属性不是响应式的
    // parent[path[path.length - 1]] = rootModule.state
    Vue.set(parent, path[path.length - 1], rootModule.state)
  }
  // 以下代码都是在处理 模块中的getters、mutations、actions
  // 挂在所有的getters
  const getters = rootModule._rowModule.getters
  if (getters) {
    Object.entries(getters).forEach(([getterName, fn]) => {
      // 将所有模块的getters都挂载在store实例的getters属性上面
      Object.defineProperty(store.getters, getterName, {
        get () {
          // 将当前模块的state -—— rootModule.state作为第一个参数传入
          return fn(rootModule.state, store.getters, rootState)
        }
      })
    })
  }
  // 挂在所有的mutations,同名的mutations会依次批量执行,所有需要用数组存贮
  const mutations = rootModule._rowModule.mutations
  if (mutations) {
    Object.entries(mutations).forEach(([mutationName, fn]) => {
      // 模块重名的时候会依次执行没有个重名的mutations,所有下面直接等于一个函数是不行的,
      // 而是要以数组的形式存起来 store.mutations = {mutationName: [mutationFn1, mutationFn2]}
      // store[mutationName] = function () {}
      const mutationsArr = store.mutations[mutationName] || []
      mutationsArr.push((payload) => {
        fn(rootModule.state, payload)
        store._subscribe.forEach(fn => fn({ type: mutationName, payload }, rootState))
      })
      store.mutations[mutationName] = mutationsArr
    })
  }
  // 挂在所有的mutations,同名的mutations会依次批量执行,所有需要用数组存贮
  const actions = rootModule._rowModule.actions
  if (actions) {
    Object.entries(actions).forEach(([actionName, fn]) => {
      // 模块重名的时候会依次执行没有个重名的actions,所有下面直接等于一个函数是不行的,
      // 而是要以数组的形式存起来 store.actions = {actionName: [actionFn1, actionFn2]}
      // store[actionName] = function () {}
      const actionsArr = store.actions[actionName] || []
      actionsArr.push((payload) => {
        fn(store, payload)
      })
      store.actions[actionName] = actionsArr
    })
  }
  // 递归挂在子模块
  Object.entries(rootModule._children).forEach(([moduleName, module]) => {
    installModule(store, rootState, path.concat(moduleName), module)
  })
}
class Store {
  constructor (options) {
    // 在注册Store创建实例对象的时候执行的函数,传入的就是store的配置对象
    // 将配置对象添加到Vuex的实例上之后,就可以在组件中通过this.$store对象访问
    // this.state = options.state // 这样直接将配置对象options.state赋值给this.state,数据不是响应式的
    // 需要借助Vue的响应式原理,通过 new Vue() 方式将this.store变成响应式的
    this.s = new Vue({
      data () {
        return { state: options.state }
      }
    })
    // 给Store实例对象$store添加getters对象属性
    this.getters = {} //
    // 给Store实例对象$store添加mutations对象属性
    this.mutations = {}
    // 给Store实例对象$store添加actions对象属性
    this.actions = {}
    // plugins
    this._subscribe = []
    this._modules = new ModuleCollection(options) // 将原数据数据格式化为一个想要的数据结构
    console.log('组装之后的state树结果:', this._modules)
    // 递归将结果进行分类处理:
    // this 整个store
    // this.state 当前的根状态
    // []为了递归创建的
    // this._modules.root 从根模块开始安装
    installModule(this, this.state, [], this._modules.root)

    // 执行plugins
    if (options.plugins) {
      options.plugins.forEach(plugin => plugin(this))
    }
  }
  subscribe = (fn) => {
    this._subscribe.push(fn)
  }
  // 给Store实例对象$store添加commit方法
  commit = (mutationName, payload) => {
    // this.mutations[mutationName](payload)
    this.mutations[mutationName].forEach(fn => fn(payload))
  }
  // 给Store实例对象$store添加dispatch方法
  dispatch = (actionName, payload) => {
    // this.actions[actionName](payload)
    this.actions[actionName].forEach(fn => fn(payload))
  }
  get state () {
    return this.s.state
  }
}
function install (_Vue) {
  // Vue.use(Vuex)的时候会执行Vuex的install方法
  console.log('install')
  Vue = _Vue
  Vue.mixin({
    // Vuex混入一个createBefore函数,每次new Vue实例的时候都会执行这个钩子函数,让后在执行组件自定义的createBefore函数
    beforeCreate () {
      // console.log('混入函数到所有vue实例中!,在这里将获取每个组件实例,并将$store注册到实例上去')
      // console.log(this) // 每个vue组件实例创建时候到配置对象 this.$options
      if (this.$options && this.$options.store) {
        // 组件的配置对象上存在store,表示这个是根组件实例对象
        this.$store = this.$options.store
      } else {
        // 其他子组建,自身的$options上是没有store对象的,可以从父组件上取之前注册上去的$store
        this.$store = this.$parent && this.$parent.$store
      }
    }
  })
}
export default {
  Store,
  install
}

四、Vuex原生实现

// my-vuex.js
let Vue
class Store {
  constructor (options) {
    this.state = new Vue({
      data () {
        return options.state
      }
    })
    // 给Store实例对象$store添加getters对象属性
    this.getters = {}
    Object.entries(options.getters).forEach(([getterName, fn]) => {
      // this.getters[getterName] = fn(this.state)
      // 这里的getters是计算属性,每次读取都需要根据state的数据动态计算。直接赋值是不行的
      Object.defineProperty(this.getters, getterName, {
        get: () => {
          return fn(this.state)
        }
      })
    })
    // 给Store实例对象$store添加mutations对象属性
    this.mutations = {}
    Object.entries(options.mutations).forEach(([mutationName, fn]) => {
      this.mutations[mutationName] = (payload) => {
        fn(this.state, payload)
      }
    })
    // 给Store实例对象$store添加actions对象属性
    this.actions = {}
    Object.entries(options.actions).forEach(([actionName, fn]) => {
      this.actions[actionName] = (payload) => {
        fn(this, payload)
      }
    })
  }
  // 给Store实例对象$store添加commit方法
  commit = (mutationName, payload) => {
    this.mutations[mutationName](payload)
  }
  // 给Store实例对象$store添加dispatch方法
  dispatch = (actionName, payload) => {
    this.actions[actionName](payload)
  }
  // 模块配置参数格式化
}
function install (_Vue) {
  // Vue.use(Vuex)的时候会执行Vuex的install方法
  console.log('install')
  Vue = _Vue
  Vue.mixin({
    // Vuex混入一个createBefore函数,每次new Vue实例的时候都会执行这个钩子函数,
    // 让后在执行组件自定义的createBefore函数
    beforeCreate () {
      console.log('混入函数到所有vue实例中!,在这里将获取每个组件实例,并将$store注册到实例上去')
      console.log(this) // 每个vue组件实例创建时候到配置对象 this.$options
      if (this.$options && this.$options.store) {
        // 组件的配置对象上存在store,表示这个是根组件实例对象
        this.$store = this.$options.store
      } else {
        // 其他子组建,自身的$options上是没有store对象的,可以从父组件上取之前注册上去的$store
        this.$store = this.$parent && this.$parent.$store
      }
    }
  })
}
export default {
  Store,
  install
}