0x1 Vuex 基础 - 安装 & 开始

301 阅读1分钟

Vuex 基础教程系列 🎉

安装

Vuex 提供了三种安装方式:CDNNPM or YarnDev Build(自行构建)。

image.png

node_modules/vuex 既将 Vuex 克隆到当前项目 node_modules 中的 vuex 目录中。

开始

StoreVuex 应用的核心,我们可以将其理解为 仓库 或者是 商店,用来存储所有的全局状态以及围绕着状态而提供的一系的管理方法。

基础示例:

<!DOCTYPE html>
<html lang="en">

<body>
  <div id="app">
			<!--通过注入的 $store 来获取 state 的值-->
      <p>{{$store.state.count}}</p>
      <button @click="add">increment</button>
  </div>
  <script>
			//创建一个 Vuex 应用
      const store = new Vuex.Store({
          state: {
              count: 0
          },
          mutations: {
              increment(state) {
                  state.count++;
              }
          }
      });

      new Vue({
          el: '#app', store, methods: {
              add() { //变更 increment mutation 的方法。
                  store.commit('increment') // 显示提交 mutation。
              }
          }
      })
  </script>
</body>

</html>

在这个示例中包含了以下几个我们必须要掌握的 Vuex 知识点:

  1. 通过 new Vuex.Store({...}) 构造函数创建一个 Vuex 应用,每一个 store 都是一个全局唯一的单例,因此我们更推荐一个应用只创建一个。
  2. state 中声明的是作为全局的状态mutations 对象则保存着对应状态的变更方法。
  3. 通过 store.state 可以访问状态中的值,但不能通过此种方式直接变更状态的值。
  4. 变更状态只能通过显示 commit mutation 方式变更 state , 不能直接 store.state 的方式变更。
  5. Vuex 支持像 VueRouter 注入 $route$router 对象那样注入一个 $store 对象。

如果想更深入的了解 Vuex 的注入机制,则必须要了解以下 Vuex 中的相关源码处理。

首先,观察 Vuex.install 方法。

function install(_Vue) {
    if (Vue && _Vue === Vue) {
        {
            console.error(
                '[vuex] already installed. Vue.use(Vuex) should be called only once.'
            );
        }
        return
    }
    Vue = _Vue;
    applyMixin(Vue);
}

然后定位到 applyMinxin 方法。

function applyMixin(Vue) {
      var version = Number(Vue.version.split('.')[0]);

      if (version >= 2) {
          Vue.mixin({ beforeCreate: vuexInit });
      } else {
          // override init and inject vuex init procedure
          // for 1.x backwards compatibility.
          var _init = Vue.prototype._init;
          Vue.prototype._init = function (options) {
              if (options === void 0) options = {};

              options.init = options.init
                  ? [vuexInit].concat(options.init)
                  : vuexInit;
              _init.call(this, options);
          };
      }

      /**
       * Vuex init hook, injected into each instances init hooks list.
       */

      function vuexInit() {
          var options = this.$options;
          //....
  }

上述代码中最核心的当属 vuexInit 方法,但是我们先放过,重点关注源码中的条件判断,可以看到 Vuex 再注入 $store 时会基于不同的 Vue 版本采用不同的注入方式。

  1. 对于 ≥ Vue 2.x 版本大于等于 2 的会使用 Vue 的 mixin 方式进行混入。
  2. 对于 < Vue2.x 版本则通过覆写 Vue.prototype.__init 方法来实现在组件实例初始化时向组件实例 this 绑定 $store ,这是一种兼容 Vue1.x 的写法,这里只提供一些上下文中最核心的代码片段。
    1. Vue.prototype.__init 方法会合并组件自带的 $options 与传入的 options 对象。

      Vue.prototype._init = function (options) {
          options = options || {}
      
          //...
      
          // merge options.
         this.$options = mergeOptions(
              this.constructor.options,
              options,
              this
          )
      
          //....
      
      		// call init hook
          this._callHook('init')
      }
      
    2. 通过执行 init hook,从而来执行我们传入的 vuexInit 方法。

      Vue.prototype._callHook = function (hook) {
          this.$emit('pre-hook:' + hook)
          var handlers = this.$options[hook]
          if (handlers) {
            for (var i = 0, j = handlers.length; i < j; i++) {
              handlers[i].call(this)
            }
          }
          this.$emit('hook:' + hook)
        }
      

最后在 vuexInit 方法的源码中,我们可以发现每次方法被执行时都会判断当前组件的 $options 是否已经存在了 store 选项,如果有则将其赋值给 this.$store 选项(当然通常有值的肯定是根组件实例)。如果没有则通过 parent.$store 选项去获取,然后同样赋值给自己的 this.$store 选项,只有这样才能保证自己的子组件也能通过 parent 获取 $store,从而使获取 $store 选项的链条不会发生断裂。

采用这种,根组件.store父组件.store ← 父组件.store ← 组件.$store ← ... 链条的方式来获取 store 本质还是依托与 Vue 组件树嵌套的结构原理。

function vuexInit() {
    var options = this.$options;
    // store injection
    if (options.store) {
        this.$store = typeof options.store === 'function'
            ? options.store()
            : options.store;
    } else if (options.parent && options.parent.$store) {
        this.$store = options.parent.$store;
    }
}