Vue2.x - Vuex

目录

 

初识Vuex

什么是Vuex

为什么要用Vuex

Vuex的基本使用

计数器案例

在项目中使用Vuex

安装Vuex到项目中

引入vuex插件并注册

创建store仓库

实现全局组件可访问的store仓库

在组件中访问store仓库中的共享数据

管理共享数据

Vuex工作流程图简析

深入理解Vuex工作流程

组件实例如何触发dispatch

action方法定义

commit方法定义

mutation方法定义

整体流程过一遍

​编辑

Vuex工作流程图的拓展知识

组件实例直接commit mutation

action和mutation的区别

在action中继续dispatch其他action

Vuex第四个核心:getters

Vuex辅助函数

Vuex中冗余的代码

mapState

mapState的作用

mapState的入参对象式写法

 mapState的入参数组式写法

mapGetters

mapGetters的作用

mapGetters入参对象式写法

mapGetters入参数组式写法

mapActions

mapActions的作用

mapActions入参对象式写法

mapActions入参数组式写法

mapMutations

mapMutations的作用 

mapMutations的入参对象式写法

mapMutations的入参数组式写法

最终改造后效果如下

Vuex模块化

什么是Vuex模块化

如何实现Vuex模块化

Vuex模块化项目结构划分

index.js的工作及注意事项

Vuex第五大核心配置modules

Vuex辅助函数的模块化适配

如何实现store.state的初始化以及持久化

基于Vuex工作流程来实现store.state的初始化和持久化

Vuex第六大核心 plugins

区分Vuex插件和Vuex的插件

Vuex的插件函数的入参以及执行时机

Vuex的插件函数中初始化state的两种方式

store.subscribe方法

案例代码分享


初识Vuex

什么是Vuex

Vuex是基于Vue开发的插件,Vuex可以集中保存和管理多个组件共享的数据

为什么要用Vuex

我们已经了解了Vuex的功能是:集中保存和管理多个组件共享的数据

那么在没有Vuex之前,我们是如何保存和管理多个组件共享数据的呢?

Vue2.x - TodoList案例_伏城之外的博客-CSDN博客https://blog.csdn.net/qfc_128220/article/details/125193079?spm=1001.2014.3001.5502在前面做的TodoList案例中,MyHeader、MyContent、MyFooter共享一个数据源,并且这些组件都需要对数据源数据进行操作。

  1. 为了方便MyHeader、MyContent、MyFooter能够获取到数据源数据,我们将数据源定义到了它们的父组件App的data中,然后通过组件标签属性传递给子组件;
  2. 另外,MyHeader、MyContent、MyFooter通过props收到数据源数据后,是不能直接通过props操作数据源的,因为props是单向数据流,props数据是只读的,Vue官方建议:数据定义在哪个组件中,数据操作就定义在该组件中,其他组件想要操作该组件数据的话,可以通过自定义事件或者全局事件总线来触发该组件的注册为事件回调的数据操作。

Vue2.x - Vuex_第1张图片

这样一来,App组件就不仅是一个管理员组件,而且还是存放共享数据的仓库,另外还要定义和暴露各种操作共享数据的方法。但是,本质上来说,App组件并不需要使用这些共享数据。

如果项目过大,则App管理的组件就越多,此时就出现了如下问题:

  • 共享数据放在App组件上,可以很方便的传递给App组件的直接子组件,如通过子组件标签属性。但是如果App组件却无法容易地将共享数据传递给它的间接子组件(如孙组件),此时有两种方案:1、逐级传递;2、全局事件总线。逐级传递自然不会考虑,使用全局事件总线地话,又会加重代码得冗余度。
  • App组件不仅要进行组件排版,还需要保存和管理共享数据,一旦项目过大,则App组件任务将越来越繁重,代码将越来越多,多人开发和后期维护难度变大。

综上所述,App组件作为共享数据的仓库适合小型项目,对于大型项目而言,App组件并不适合保存和管理共享数据。

而Vuex是一个独立于组件之外,可以被所有组件访问到的保存和管理共享数据的仓库。

Vuex的基本使用

计数器案例

计数器是一个很适合学习Vuex的案例,下面是未使用Vuex的计数器实现:

Vue2.x - Vuex_第2张图片

演示效果:

Vue2.x - Vuex_第3张图片

 下面我们将一边学习Vuex,一边改造计数器案例。

在项目中使用Vuex

安装Vuex到项目中

(注意:vue2对应vuex3,vue3对应vuex4)

npm i vuex@3

引入vuex插件并注册

Vue2.x - Vuex_第4张图片

创建store仓库

Vuex插件的目的是创建一个存放和管理共享数据的仓库,而在引入的Vuex对象上有一个Store构造函数,该构造函数可以创建一个store实例,store实例就是存放和管理共享数据的仓库,共享数据存放在创建store实例的Vuex.Store的入参配置对象的state属性中。比如计数器案例中,count值就可以看出一个共享数据。

Vue2.x - Vuex_第5张图片

实现全局组件可访问的store仓库

现在我们已经有了store仓库,接下来就是需要让所有组件实例都能访问到这个store仓库。Vuex的实现方案是:在Vue构造函数入参配置对象中扩展一个配置属性store,该配置属性接收一个Vuex.Store实例,一旦该配置被解析,则vm管理的所有组件实例上都会挂载一个$store属性,值就是new Vue时传入的options.store值。

new Vuex.Store => (vm.)options.store => (vc.)$store

Vue2.x - Vuex_第6张图片

在组件中访问store仓库中的共享数据

而在组件实例的$store属性对象上可以访问到 store仓库实例的state属性,这意味着所有组件实例都可以访问到共享数据了

Vue2.x - Vuex_第7张图片

所以计数器案例中,我们可以将共享数据count从App组件中转移到Vuex仓库中定义,然后App组件也支持直接从自身$store.state上获取到count

Vue2.x - Vuex_第8张图片

至此我们已经实现了:将共享数据存放到基于Vuex实现的store仓库中,并且在任意组件中访问到store仓库中的共享数据。

管理共享数据

我们知道Vuex的功能是保存和管理多个组件共享的数据。目前保存已经实现了,但是如何管理呢?

Vuex工作流程图简析

Vue2.x - Vuex_第9张图片

上面图示,就是Vuex的工作流程,首先我们对其中各个要素进行解释:

从图中虚线可以看出,Vuex是由三大核心组成的:Actions、Mutations、State。

Actions、Mutations、State其实都是Vuex.Store构造函数入参配置对象的属性,即它们都是创建store仓库实例的重要配置。

Vue2.x - Vuex_第10张图片

Actions对象中存放了各种action方法,Mutations对象中存放了各种mutation方法。

从图示中可以看出,action方法的职责有三个:

  • 接收组件实例触发dispatch传递过来的数据
  • 对接后端服务器接口,处理数据
  • 触发commit,提交处理结果给Mutations对象中的mutation方法

而mutation方法的的职责有三个:

  • 接收action方法触发commit传递过来的处理结果
  • 将数据保存进store.state中
  • 记录本次操作到Vue DevTools中

State对象是保存具体共享数据的地方,是实际意义的仓库。另外,我们需要注意的是:

state对象中保存的共享数据都是响应式的

Vue2.x - Vuex_第11张图片

它们的响应式逻辑和组件的data属性响应式原理一致,只要数据发生改变,使用数据的组件的模板就会被重新解析渲染。这也就是流程图中,State对象的render线的工作。

通过上面对于Vuex三大核心的工作内容解释,我们可以隐约知道:actions和mutations就是Vuex管理共享数据的方式。

深入理解Vuex工作流程

组件实例如何触发dispatch

目前我们在组件中已经可以通过$store.state.count访问到共享数据了,那么如何操作共享数据呢?按照流程图看,组件实例需要触发一个dispatch,那么dispatch是啥,又在哪呢?

dispatch是一个Vuex.Store.prototype上的方法

Vue2.x - Vuex_第12张图片

所以只要有store实例,就可以访问到disptch方法,而组件实例上的$store属性就是一个store实例。

dispatch方法有两个入参:

  1. type:分发给哪个action方法,值为action方法的名字
  2. payload: 传递给action方法的数据(建议传对象)

action方法定义

其实定义在actions中的action方法,类似于事件绑定的回调函数,action方法名就是事件类型。

而组件实例调用dispatch方法就是,触发指定事件类型(type),并传入数据(payload)给事件回调(action方法)。

action方法有两个入参:

  1. context:执行上下文
  2. payload:接收dispatch传来的payload

action方法是Vuex底层自动调用的,所以context入参和payload入参也是自动传的,其中payload没有什么问题,而context是什么?又来用干什么工作呢?

Vue2.x - Vuex_第13张图片

Vue2.x - Vuex_第14张图片打印发现context是一个Object对象,它身上有:

  • commit方法:该方法是用于实现action方法内提交数据到指定mutation方法
  • dispatch方法:当处理数据的逻辑比较复杂时,即一个action方法处理吃力时,可以使用context.dispatch继续调用另一个action方法,形成action方法调用链,将复杂逻辑分解为多个简单逻辑来处理,更加有利于逻辑复用。
  • state属性:该属性就是store.state,因为我们在action方法中是有可能需要使用共享数据的,比如计数器案例中偶数加1,只有共享数据count为偶数时,才能继续加1。
  • ......

context将action方法中可能用到的东西都准备好了,所以称为它为执行上下文。

commit方法定义

commit方法可以提交action方法的处理结果到mutation方法中,commit方法入参有两个:

  1. type:提交到哪个mutation方法,值为mutation方法的名字
  2. payload:action处理结果

Vue2.x - Vuex_第15张图片

mutation方法定义

为了区别action和mutation方法,通常将mutation方法名定义为action方法名的全大写形式,如果action方法名由多个单词组成,则多个单词间由"_"连接。

mutation方法的入参有两个:

  1. state:$store.state
  2. payload:commit提交的action处理结果

mutation方法的作用是将commit提交的处理结果保存到$store.state中。

Vue2.x - Vuex_第16张图片

整体流程过一遍

Vue2.x - Vuex_第17张图片

dipatch => action => commit => mutation

当mutation完成state的数据修改后,就会触发响应式行为,引起使用该数据的组件模板的重新渲染。

Vuex工作流程图的拓展知识

组件实例直接commit mutation

按照Vuex工作流程图,组件实例必须通过dispatch分发数据给action,然后由action提交数据给mutation。

但是我们发现某些action其实只是做了一个数据透传的工作,这意味着action的定义变得冗余。

Vue2.x - Vuex_第18张图片

此时组件实例可以直接commit数据给mutation,即跳过action。

Vue2.x - Vuex_第19张图片

action和mutation的区别

action和mutation都是用于管理共享数据的,二者的区别主要在于:

  • action中可以对接后端API,进行异步操作,而mutation不可以,mutation只能进行同步操作。因为mutation的对于state的操作结果需要同步记录到Vue DevTools中。
  • state数据的修改只能在mutation中完成,而action中虽然可以通过context入参获取到state,但是不建议在action中修改state数据,主要原因是:mutation的操作可以被记录到Vue DevTools中,而action中操作state不会被记录到Vue DevTools中

Vue2.x - Vuex_第20张图片Vue2.x - Vuex_第21张图片

将异步操作放到mutation中,将导致Vue DevTools中记录的mutation动作的state结果和实际结果不一致。 

Vue2.x - Vuex_第22张图片Vue2.x - Vuex_第23张图片

直接在action中修改state,即不经过mutation,将导致Vue DevTools无法记录state的修改。

从上面两个测试结果来看,在action中修改state、在mutation中进行异步操作对于实际效果无影响,影响的只是Vue DevTools中的mutation记录。

我们可以在Vuex.Store入参配置对象中新增一个属性strict,设为true,表示严格按照 action中不能修改state,mutation中不能进行异步操作的标准进行检查,若不符合标准,则报错。

Vue2.x - Vuex_第24张图片

这里state的修改是在action中进行的,所以报错state不能在mutation以外执行。 

Vue2.x - Vuex_第25张图片

 这里延迟加也是报错 state不能在mutation之外执行,因为state的修改是在setTimeout的异步回调 ()=>{} 中执行的。而不是在INCREMENT_ASYNC中执行的。

在action中继续dispatch其他action

action方法的入参context上,除了有state、commit外,还有dispatch,这意味着action方法中可以继续dispatch其他action。

实际业务场景中,我们经常会遇到一些复杂功能的业务,此时将整个复杂功能定义在一个action中,不仅降低了代码可读性,可维护性,而且一些可复用的功能也无法复用。所以我们需要将复杂功能分解为多个独立的简单功能,然后将每个简单功能定义为一个action,然后依次在action中dispatch到下一个action,像一个链条一样。

Vue2.x - Vuex_第26张图片

Vuex第四个核心:getters

现在我们需要让计数器新增展示一个count * 10的值。

按照一般的思路,我们直接在组件内新增一个computed计算属性即可。

Vue2.x - Vuex_第27张图片

 那么,如果这个count * 10值如果也是一个共享数据呢?此时需要在store.state中新定义一个属性吗?

答案是不需要,在store中还有一个配置属性getters,它相当于共享数据的计算属性。

Vue2.x - Vuex_第28张图片

组件实例需要通过 $store.getters.count10访问

Vue2.x - Vuex_第29张图片

Vuex辅助函数

Vuex中冗余的代码

Vue2.x - Vuex_第30张图片

在模板语法中访问store仓库中的共享数据,则必须要带$store.state或$store.getters前缀,形式上很冗余,此时我们可以使用计算属性来取出模板语法中的代码冗余

Vue2.x - Vuex_第31张图片

但是此时冗余度来到了计算属性中。这些代码形式非常公式化。

Vue2.x - Vuex_第32张图片

另外,dispatch和commit方法调用也是非常公式化的。

Vuex提供了四个辅助函数来帮助开发者自动生成上述公式化的代码。

mapState

mapState的作用

由于将 store.state.xxx 投射为 组件内 计算属性 yyy 的形式过于固定。如:

yyy(){
    return this.$store.state.xxx
}

其中yyy,xxx为动态的,其他的都是静态的。所以Vuex提供了mapState方法用于生成组件内计算属性函数,我们只需要给mapState提供xxx,yyy信息即可。

mapState的入参对象式写法

mapState可以接收一个对象,对象中可以定义多个属性:

  • 属性名就是yyy(组件内计算属性的名字)
  • 属性值就是xxx(store.state仓库中共享数据的属性名字)

Vue2.x - Vuex_第33张图片

mapState函数的返回值是一个对象,该返回值对象包含自动生成的(组件内)计算属性方法。

其实mapState的实现自动生成计算属性逻辑很简单,大致如下:

function mapState(options) {
    const result = {}

    for(let yyy in options) { // yyy是组件内计算属性名字
        const xxx = options[yyy] // xxx是store.state仓库中共享数据属性的名字
        
        const mappedState = function(){ // 自动生成的计算属性函数
            return this.$store.state[xxx]
        }

        result[yyy] = mappedState
    }

    return result
}

得到了mapState返回值对象后,我们只需要将mapState返回值对象展开,合并入组件的computed对象即可。

Vue2.x - Vuex_第34张图片

 mapState的入参数组式写法

mapState入参对象式写法适用于:【组件内计算属性名字】和【store.state仓库内共享数据属性名字】不一样时使用。

如:...mapState({myCount: 'count'}) ,计算属性名字为myCount,仓库中共享数据属性名字为count

当【组件内计算属性名字】和【store.state仓库内共享数据属性名字】一样时,这种对象式写法也存在一点冗余:

...mapState({count: 'count'})

此时mapState还支持传入一个数组,数组元素就是【组件内计算属性名字】和【store.state仓库内共享数据属性名字】一样时的名字。

...mapState(['count'])

mapState的入参数组式写法 算是 对象式写法 的 简写形式。

实现也很简单:

function mapState(options) {
    // 数组式 转为 对象式
    if(Array.isArray(options)) {
        const newOptions = {}
        options.forEach(item => {
            newOptions[item] = item
        })
        options = newOptions
    }

    // 对象式逻辑
    const result = {}

    for(let yyy in options) { // yyy是组件内计算属性名字
        const xxx = options[yyy] // xxx是store.state仓库中共享数据属性的名字
        
        const mappedState = function(){ // 自动生成的计算属性函数
            return this.$store.state[xxx]
        }

        result[yyy] = mappedState
    }

    return result
}

mapGetters

mapGetters的作用

由于将 store.getters.xxx 投射为 组件内 计算属性 yyy 的形式过于固定。如:

yyy(){
    return this.$store.getters.xxx
}

其中yyy,xxx为动态的,其他的都是静态的。所以Vuex提供了mapGetters方法用于生成组件内计算属性函数,我们只需要给mapGetters提供xxx,yyy信息即可。 

mapGetters入参对象式写法

当【组件内计算属性名字】和【store.getters仓库内共享计算属性的名字】不一样时,使用mapGetters入参对象式写法

Vue2.x - Vuex_第35张图片

mapGetters入参数组式写法

当【组件内计算属性名字】和【store.getters仓库内共享计算属性的名字】一样时,使用mapGetters入参数组式写法

Vue2.x - Vuex_第36张图片

mapActions

mapActions的作用

由于组件实例调用dispatch方法分发action的形式过于固定,如

    incrementOdd(){
      this.$store.dispatch('incrementOdd', {num: this.num})
    },

 其中 incrementOdd,'incrementOdd'是动态的,{num:this.num}是动态的,其余都是固定的。

当我们需要独立生成函数时,函数内参数只能来源于函数入参,所以上述代码可以改造为:

    incrementOdd(val){
      this.$store.dispatch('incrementOdd', val)
    },

则此时,只有incrementOdd,'incrementOdd'是动态的,其余都是静态的。

所以Vuex提供了mapActions来生成这段代码

    yyy(val){
      this.$store.dispatch(xxx, val)
    },

yyy是组件内method方法名子,xxx是store.actions中action方法名字

mapActions入参对象式写法

mapActions入参对象式写法适用于:【组件内method方法名】 和 【store.actions中方法名】不一致时

mapActions({yyy:xxx})

Vue2.x - Vuex_第37张图片

需要注意的是,mapActions返回的对象中的method方法(即自动生成的methods方法),其入参将作为dispatch的第二个参数。

所以我们需要在使用自动生成的mtehods方法时,按需传入数据。如上例中,incrementOdd作为事件回调时,传入了对象{num}作为事件回调入参。

mapActions入参数组式写法

mapActions入参数组式写法适用于:【组件内method方法名】 和 【store.actions中方法名】一致时,该种写法算是对象式写法的简写形式。

Vue2.x - Vuex_第38张图片

mapMutations

mapMutations的作用 

由于组件实例调用commit方法提交mutation的形式过于固定,如

    increment(){
      this.$store.commit('INCREMENT', {num: this.num})
    },

其中increment和“INCREMENT”是动态的,{num:this.num}也是动态的,其余是静态的。

当我们需要独立生成函数时,函数内参数只能来源于函数入参,所以上述代码可以改造为:

    increment(val){
      this.$store.commit('INCREMENT', val)
    },

所以只有increment和“INCREMENT”是动态的,其余是静态的。

所以Vuex提供了mapMutations来生成这段代码

    yyy(val){
      this.$store.commit(xxx, val)
    },

yyy是组件内method方法名字,xxx是store.mutations中mutation方法名字

mapMutations的入参对象式写法

mapMutations入参对象式写法适用于:【组件内method方法名】 和 【store.mutations中方法名】不一致时 

Vue2.x - Vuex_第39张图片

需要注意的是,利用mapMutations自动生成的组件内method,需要给定入参。

mapMutations的入参数组式写法

mapMutations入参数组式写法适用于:【组件内method方法名】 和 【store.mutations中方法名】一致时,此种写法算是对象式写法的简写形式。

需要注意的是,利用mapMutations自动生成的组件内method,需要给定入参。

最终改造后效果如下

Vue2.x - Vuex_第40张图片

Vuex模块化

什么是Vuex模块化

当前我们将Vuex仓库创建在了main.js中,并且所有业务的共享数据及管理共享数据的行为都定义了一起。我们可以将计数器案例和TodoList案例合并在一起。

虽然此时store仓库只有两个共享数据,及它们的管理行为,但是一眼看去也非常乱。

所以,我们需要将store仓库中数据及数据管理行为 按照 业务 进行解耦,定义到不同模块中,然后将不同模块引入到store仓库中,而这就是Vuex的模块化。

如何实现Vuex模块化

Vuex模块化项目结构划分

按照Vuex官方建议,我们需要在src目录下创建一个store文件夹用于存放Vuex仓库实现及模块化相关代码。

Vue2.x - Vuex_第41张图片

index.js的工作及注意事项

首先我们需要将store仓库地生成转移到 src/store/index.js中完成,然后再在 src/main.js 中导入  src/store/index.js 暴露出去的store实例。

Vue2.x - Vuex_第42张图片

需要注意的是,Vuex插件的注册,即Vue.use(Vuex)需要在new Vuex.Store之前执行,否则报错。

所以下面这种书写方式会报错:

Vue2.x - Vuex_第43张图片

 有人可能认为将 import store from xxx 放到 Vue.use(Vuex)之后即可修复错误,其实不然,实际上依旧会报同样的错误,

Vue2.x - Vuex_第44张图片

原因是:

ES6的import是在静态编译时执行的,即在main.js尚未执行前,main.js中所有import就已经完成了和export接出接口对接,当main.js开始执行时,优先获取import接入接口变量,然后才运行其他非import语句。所以,import有提升作用。关于ES6模块化可以参考下:

随笔-深入理解ES6模块化(三)_伏城之外的博客-CSDN博客https://blog.csdn.net/qfc_128220/article/details/121950458?spm=1001.2014.3001.5501或者阮一峰大神的ES6模块化教程说明:

Module 的语法 - ECMAScript 6入门 (ruanyifeng.com)https://es6.ruanyifeng.com/#docs/module

Vuex第五大核心配置modules

Vuex模块化旨在将原本多个业务数据源杂糅存在在一起的store,根据不同业务进行数据分离,分成多个不同模块,每个模块都有自己的actions、mutations、state、getters。

而src/store/modules文件夹就是用于收集模块文件的。

模块文件最终需要对外暴露一个包含actions、mutations、state、getters等的配置对象,暴露的配置对象最终被index.js引入,并以模块化的方式传给Vuex.Store入参配置对象的modules配置。

Vue2.x - Vuex_第45张图片

Vuex.Store入参配置对象的modules属性是Vuex的第五大核心,它的作用是:让我们访问store仓库中共享数据时,或者使用store仓库中管理共享数据的action或mutation行为时也要按照模块化方式进行。

举个简单的例子:

没有使用Vuex模块化前,我们在组件模板语法中访问共享数据,一般如下:

$store.state.count

而使用了Vuex模块化后,我们需要这样访问:

$store.state.countAbout.count 

Vue2.x - Vuex_第46张图片

这里countAbout、todosAbout就是我们创建store实例时,传入Vuex.Store入参配置对象的modules属性对象的属性

Vue2.x - Vuex_第47张图片

同样地,不仅是state数据访问受到了模块化影响,其他actions、mutations、getters中方法的使用也一样收到模块的影响,调用action方法、mutation方法、getters属性时同样要添加countAbout、todosAbout前缀。

Vue2.x - Vuex_第48张图片

对于state属性的访问,只需要将以前的 $store.state.xxx 变为 $store.state.模块名.xxx 即可

对于action、mutation方法和getters方法的调用,则需要改变组件实例调用dispatch和commit方法传入type参数,如以前为 'CHECK_ALL_TODO' ,现在则要变为 '模块名/CHECK_ALL_TODO' 

但是我们目前已经使用mapXxx辅助函数来生成对应state、action、mutation、getters的访问或调用代码,那么mapXxx是否也能适配模块化呢?

Vuex辅助函数的模块化适配

mapState、mapActions、mapMutations、mapGetters这些方法之前说明时,一般都是一个对象入参,或者一个数组入参。但是实际上,他们还有一个首位可选参数:命名空间。

  • mapState(namespace?: string, map: Array | Object): Object
  • mapActions(namespace?: string, map: Array | Object): Object
  • mapMutations(namespace?: string, map: Array | Object): Object
  • mapGetters(namespace?: string, map: Array | Object): Object

这个可选的namespace入参就是用于进行模块化适配的,mapXxx的入参namespace值为Vuex.Store入参配置对象的modules属性设置给模块的名称

Vue2.x - Vuex_第49张图片

Vue2.x - Vuex_第50张图片

但是想要使mapXxx的入参namespace值起作用,模块的暴露的配置对象的必须开启命名空间,即需要配置namespaced:true

Vue2.x - Vuex_第51张图片

此时mapXxx辅助函数就可以根据namespace入参值进行模块化适配后的自动生成行为了。 

我们以mapState为例,实现下:

function mapState(namespace, options) {
    let isModule = false

    if(typeof namespace !== 'string' && typeof options === 'undefined') {
        options = namespace
    } else {
        isModule = true
    }

    // 数组式 转为 对象式
    if(Array.isArray(options)) {
        const newOptions = {}
        options.forEach(item => {
            newOptions[item] = item
        })
        options = newOptions
    }

    // 对象式逻辑
    const result = {}

    for(let yyy in options) { // yyy是组件内计算属性名字
        const xxx = options[yyy] // xxx是store.state仓库中共享数据属性的名字
        
        const mappedState = function(){ // 自动生成的计算属性函数
            if(isModule) {
                return this.$store.state[namespace][xxx]
            } else {
                return this.$store.state[namespace][xxx]
            }
            
        }

        result[yyy] = mappedState
    }

    return result
}

如何实现store.state的初始化以及持久化

之前的TodoList案例中,所有的todo都是从浏览器的localStorage中读取的,最终也是保存在浏览器的localStorage中,实现过程是:

  • 当App组件mounted后,就从localStorage中读取todo
  • 深度监听todos,一旦todos发生改变,则将todos写入localStorage

使用Vuex后,我们将todos保存在了store.state中,那么如何实现store.state的初始化,以及当store.state数据改变时将其持久化呢?

目前,store.state中的数据只能预置,即提前写死,并且一旦网页刷新,则store.state就会被重置。

基于Vuex工作流程来实现store.state的初始化和持久化

我们知道 store.state的数据可以被 组件实例调用dispatch或commit,且最终通过mutation方法修改掉,所以我们只需要找到App组件,在其mounted钩子中 读取localStorage中todos数据并通过dispatch或commit传给store.state 

Vue2.x - Vuex_第52张图片

并且在App组件中深度监听store.state.todos的变动,一旦改变,则取出 store.state.todos持久化到localStorage中

Vue2.x - Vuex_第53张图片

此时就能完成store.state.todos的初始化和持久化。

但是这种方式给App组件增加了负担,不值得推荐。

Vuex第六大核心 plugins

区分Vuex插件和Vuex的插件

首先我们要区别下:【Vuex插件】 和 【Vuex的插件】

【Vuex插件】是指Vuex本身是一个基于Vue开发的插件,即Vuex是Vue的插件。

【Vuex的插件】是指Vuex插件的插件。

我们一般将【Vuex的插件】配置为 创建store实例的Vuex.Store入参配置对象的plugins数组的元素。

Vue2.x - Vuex_第54张图片

Vuex的插件函数的入参以及执行时机

 【Vuex的插件】是一个函数,该函数会接收到一个所在的store实例作为入参,且该函数的调用时机是store实例初始化完成后。

所以,在 【Vuex的插件】函数中我们可以完成store.state的初始化。

Vue2.x - Vuex_第55张图片

 但是此时报错提示,我们不能在插件函数中修改store.state,那不完犊子了吗....

Vuex的插件函数中初始化state的两种方式

此时我们需要借助一个store实例方法

Vue2.x - Vuex_第56张图片

这个方法可以在mutation方法外替换整个store.state,而不会触发state只能在mutation中修改的报错。

Vue2.x - Vuex_第57张图片

这里先是利用JSON.parse(JSON.stringify)来进行了store.state对象的深度拷贝,然后基于拷贝对象初始化了todos,最后将拷贝对象替换掉原来的store.state。

又或者使用下面这种方式:即严格按照只能在mutation中修改state的标准来搞。

Vue2.x - Vuex_第58张图片

store.subscribe方法

store实例还可以调用一个实例方法subscribe,该方法接收一个handler函数作为参数。

而handler函数:会在每个 mutation 完成后调用,接收 mutation 和经过 mutation 后的状态state作为参数

由于handler函数的调用时机是在每个mutation完成后,即store.state改变后,相当于handler函数深度监听了store.state的变化,一旦store.state发生变化,则handler被调用。

所有我们可以在handler函数中完成store.state的持久化动作。

Vue2.x - Vuex_第59张图片

但是上面逻辑存在一个问题,即handler是监听整个state的深度变化,所以不管是state.todosAbout.todos的变动,还是state.countAbout.count的变动,都会触发handler的执行,这将会造成严重的性能问题。

此时,我们需要借助handler的mutation入参,该入参是一个对象,具有如下属性:

  • type:哪个mutation触发的state改变
  • payload:该mutation的数据

Vue2.x - Vuex_第60张图片

我们可以根据mutation.type的前缀来判断是哪个模块的state数据发生了改变,比如上面type值为“todosAbout/ADD_TODO”,则说明是todosAbout模块的state发生改变。

Vue2.x - Vuex_第61张图片

此时handler引发的性能问题将得到缓解。但是仍然需要注意防抖和节流。 

案例代码分享

qwx427219/Todos_Count_Vuex: 基于Vuex的 Todo List & Count 整合案例 (github.com)icon-default.png?t=M4ADhttps://github.com/qwx427219/Todos_Count_Vuex

你可能感兴趣的:(Vue,vue,vuex)