阅读 1543

技术总结 | 认真梳理一遍 Vuex 的 Life Cycle,迎接Vue3.0

Vuex简介

在使用Vue.js进行开发大型,且数据复杂的应用时,都会遇到多个组件共享同一个状态,亦或是多个组件会去更新同一个状态,在应用代码量较少的时候,我们可以组件间通信去维护修改数据,或者是通过事件总线来进行数据的传递以及修改。但是当应用逐渐庞大以后,代码就会变得难以维护,从父组件开始通过prop传递多层嵌套的数据由于层级过深而显得异常脆弱,而事件总线也会因为组件的增多、代码量的增大而显得交互错综复杂,难以捋清其中的传递关系。

Vuex就是把数据层放到全局形成一个单一的Store,组件层变得更薄,专门用来进行数据的展示及操作。所有数据的变更都需要经过全局的Store来进行,形成一个单向数据流,使数据变化变得“可预测”。

Vuex是一个专门为Vue.js框架设计的(为什么呢?等会看源码,知道了)、用于对Vue.js应用程序进行状态管理的库,它借鉴了Fluxredux的基本思想,将共享的数据抽离到全局,以一个单例存放,同时利用Vue.js的响应式机制来进行高效的状态管理与更新。正是因为Vuex使用了Vue.js内部的“响应式机制”,所以Vuex是一个专门为Vue.js设计并与之高度契合的框架(优点是更加简洁高效,缺点是只能跟Vue.js搭配使用)

Vue组件之间通信方式

父传子(props down

父组件向子组件传值,在父组件内,通过设置子组件的属性给子组件传值,再子组件内,通过props属性来接受子组件传来的属性 图二中type:为 String

子传父(events up

子组件向父组件传值时,在子组件内通过触发内部的事件来给父组件传值,或者是更改父组件的状态

事件共享(event bus

事件共享也叫事件巴士,通过一个事件中心,绑定不用的事件名称,其他组件可以出发某一个事件也改变绑定事件的值

$attrs/$listeners

$attrs$listeners的主要应用是实现多层嵌套传递,父组件内,通过对子组件使用v-bind/v-on绑定属性/事件,子组件内则可以通过$attrs/$listeners接受(在props内的属性,不会出现在$attrs),并可以多级嵌套传递

Tip:另外补充一个Vue组件属性的 inheritAttrsVue官网对于inheritAttrs的属性解释:如果你不希望组件的根元素继承特性,你可以在组件的选项中设置 inheritAttrs: false。该属性默认为true,具体效果看图

provide/inject

provide/inject主要使用在跨层级组件之间的传值。provide 是在父组件中定义,然后所有子组件都是可以通过 inject 注入该变量进行操作 这里变量内容不一致,应该是grandpa.msg

ref$parent/$children

ref 被用来给DOM元素或子组件注册引用信息。引用信息会根据父组件的 $refs 对象进行注册。如果在普通的DOM元素上使用,引用信息就是元素; 如果用在子组件上,引用信息就是组件实例

$children指向直接子组件。需要注意 $children 并不保证顺序,也不是响应式的。如果你发现自己正在尝试使用 $children 来进行数据绑定,考虑使用一个数组配合 v-for 来生成子组件,并且使用 Array 作为真正的来源。 $parent 指向当前组件树的根 Vue 实例。如果当前实例没有父实例,此实例将会是其自己。

Vuex的使用

概念分析

vuex单一状态(全局状态)管理,在SPA单页面组件的开发中,在state中定义了一个数据之后,你可以在所在项目中的任何一个组件里进行获取、修改,并且你的修改可以同步全局。

可以更形象的把他称为一个管理数据的仓库,把多个组件(不限层级)都需要的state,全部都放在仓库里进行保存和操作,如果你的项目状态比较复杂,模块比较多。Vuex还支持modules属性,来帮助你管理这些模块,下面是一个Vuex.Store的配置参数

  • state

    单一状态树,用来保存状态的对象,类似于组件中的data

  • getters

    state中延伸出一些方便使用的状态,类似于组件中的computed,官网是这么解释的:

    Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

  • mutations

    更改 Vuexstore 中的状态的唯一方法。非常类似于事件,每个 mutation 都有一个字符串的 事件类型 type 和 一个 回调函数 handler。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数。

  • actions

    action类似于mutation,比mutation更强大,但是不能直接修state,需要出发特定的mutation来修改state,通常用来异步的操作state

  • modules

    应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。可以将store划分成模块,每一个模块类似于一个store

实例方法

  • commit

    store.commit有两种参数方式

    store.commit('module?/mutation', { num: 1})
    store.commit({ type: 'mutation', num: 1 })
    复制代码
  • dispatch store.dispatch同样有两种参数方式

    store.dispatch('module?/action', { num: 1})
    store.dispatch({ type: 'action', num: 1 })
    复制代码
  • watch 响应式地监测一个 getter 方法的返回值,当值改变时调用回调函数。

    store.watch((state, getter) => state.app.num,()=>{ console.log('state.app.num changed'), {}})
    复制代码
  • registerModule 注册一个动态模块

    store.registerModule(moduleObject)
    复制代码
  • hotUpdate 使用 webpackHot Module Replacement,Vuex 支持在开发过程中热重载 mutationmoduleactiongetter。没有太好的例子,上官网的code

    // webpack api 注册所有模块
    function loadModules() {
      const context = require.context("./modules", false, /([a-z_]+)\.js$/i)
    
      const modules = context
        .keys()
        .map((key) => ({ key, name: key.match(/([a-z_]+)\.js$/i)[1] }))
        .reduce(
          (modules, { key, name }) => ({
            ...modules,
            [name]: context(key).default
          }),
          {}
        )
    
      return { context, modules }
    }
    const { context, modules } = loadModules()
    const store = new Vuex.Store({
    	modules
    })
    
    if (module.hot) {
      // 在任何模块发生改变时进行热重载。
      module.hot.accept(context.id, () => {
        const { modules } = loadModules()
    
        store.hotUpdate({
          modules
        })
      })
    }
    复制代码
  • subscribe/subscribeAction

    订阅 storemutation/actionhandler 会在每个 mutation/action 完成后调用,接收 mutation/action 和经过 mutation/action 后的状态作为参数。

辅助函数

辅助函数的诞生,更方便的来调用store里面的方法,同时使用辅助函数,可以更好的让我们知道在某个组件内调用了那些store,而不是用store的实例,来到处的this.$store.committhis.$store.dispatch。在项目上了一个层级,以及后起维护的时候,就不知道哪里调用了store的方法,改变了state

  • mapState

    当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性

  • mapGetters

    mapGettersstore 中的 getter 映射到局部计算属性

  • mapMutations

    使用 mapMutations 辅助函数将组件中的 methods 映射为 store.commit 调用

  • mapActions

    使用 mapActions 辅助函数将组件中的 methods 映射为 store.dispatch 调用

定义仓库 组件中使用辅助函数(例子)

阅读Vuex源码

上面说了Vuex的那么多概念和方法,到底Vuex的里面藏了什么。我们就来看看Vuex的五脏六腑吧(node_modules/vuex, version:3.4.0 为示例)

package目录

Vuex的五脏六腑

着重看一下vuex/dist/vuex.js

  • install方法 Vue在使用Vue.use(plugin)时,会调用 plugininstall 方法,我们就从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);
    }
    复制代码

    install调用applyMixin并传入Vue

  • applyMixin方法

    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;
        // 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;
        }
      }
    }
    复制代码

    根据Vue版本选择初始化方法 调用vuexInit,然后获取options.store,如果options没有store$store指向parent.store,如果有store根据类型执行

  • store里面有什么

    • 370 ~ 436Store的构造函数函数,生成store对象
    • 448 ~ 631行 在Store的原型上添加实例方法
    • 708 ~ 758行 初始化module相关配置
    • 818 ~ 890行 注册getters, mutations, actions, modules
    • 937 ~ 1070行 编写辅助函数mapState
    • 1077~ 1140行 模块命令空间的操作
    • 1143~ 1196logger函数 (大佬就是大佬,还要备注一下借鉴了redux-logger,在1141行)

手动实现一个Vuex

我们按照Vuex的思路来实现一个简易个Vuex,读起来有些别扭~

编写install方法

声明store类,并写入实例方法

注册自定义store

在组件中使用

结束语

九月,透蓝的天空,悬着火球般的太阳,云彩好似被太阳烧化了,也消失得无影无踪。望着远处的房屋之上,缓缓升起的炊烟。身体颤了一颤,抡起键盘继续优化我的code

如果对你有帮助,请不要吝啬你的👍

文章最后送上demo飞机票