【Vue】全家桶之Vuex

z@[toc]

概述

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

组件间共享数据的方式

  • 父向子传值:v-bind属性绑定
  • 子向父传值:v-on事件绑定
  • 兄弟组件之间共享数据:EventBus
  • $on 接收数据的组件
  • $emit 发送数据的组件

想象一个场景

如果你的项目里有很多页面(组件/视图),页面之间存在多级的嵌套关系,此时,这些页面假如都需要共享一个状态的时候,此时就会产生以下两个问题:

  • 多个视图依赖同一个状态
  • 来自不同视图的行为需要变更同一个状态

动动你的小脑袋你就会想到解决以上方法的方案:

  • 对于第一个问题,假如是多级嵌套关系,你可以使用父子组件传参进行解决,虽有些麻烦,但好在可以解决;对于兄弟组件或者关系更复杂组件之间,就很难办了,虽然可以通过各种各样的办法解决,可实在很不优雅,而且等项目做大了,代码就会变成屎山,实在令人心烦。
  • 对于第二个问题,你可以通过父子组件直接引用,或者通过事件来变更或者同步状态的多份拷贝,这种模式很脆弱,往往使得代码难以维护,而且同样会让代码变成屎山。

此时,既然思考到了这里,如果换一种思路呢:

  • 把各个组件都需要依赖的同一个状态抽取出来,在全局使用单例模式进行管理。
  • 在这种模式下,任何组件都可以直接访问到这个状态,或者当状态发生改变时,所有的组件都获得更新。

这时候,Vuex诞生了!

这就是 Vuex 背后的基本思想,借鉴了 Flux、Redux。与其他模式不同的是,Vuex 是专门为 Vue 设计的状态管理库,以利用 Vue.js 的细粒度数据响应机制来进行高效的状态更新。

官网的vuex使用周期图
在这里插入图片描述

安装

npm install vuex --save

使用

导入

import Vuex from 'vuex'
Vue.use(Vuex)

创建store对象

const store = new Vuex.Store({
    
    
    // state中存放的就是全局共享数据 
    state:{
    
         
    count: 0 
    } 
})

挂载store对象

new Vue({
    
     
    el: '#app', 
    render: h=>h(app)m 
    router, 
    //将创建的共享数据对象,挂载到Vue实例中
    //所有的组件,就可以直接从store中获取全局的数据了
    store 
})

创建带vuex的vue项目

  1. 打开终端,输入命令:vue ui
  2. 当项目仪表盘打开之后,我们点击页面左上角的项目管理下拉列表,再点击Vue项目管理器
  3. 点击创建项目
  4. 设置项目名称和包管理器
  5. 设置手动配置项目
  6. 设置功能项
  7. 创建项目

项目代码格式化:

在项目根目录(与src平级)中创建 .prettierrc 文件,编写代码如下:

{
    
    
    "semi":false,
    "singleQuote":true
}

核心概念

State

概念
State是提供唯一的公共数据源,所有共享的数据都要统一放到`Store的State中进行存储。

如果状态信息是保存到多个Store对象中的,那么之后的管理和维护等都会变得特别困难,所以Vuex也使用了单一状态树(单一数据源Single Source of Truth)来管理应用层级的全部状态。

单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便的管理和维护。

export default new Vuex.Store({
    
      
    state: {
    
        
        count: 0  
    }
}

State数据访问方式一

通过this.$store.state.全局数据名称访问,eg.

<h3>当前最新Count值为:{
   
   {this.$store.state.count}}</h3>

State数据访问方式二

从vuex中按需导入mapState函数

import {
    
     mapState } from 'vuex'

通过刚才导入的mapState函数,将当前组件需要的全局数据,映射为当前组件的computed计算属性:

<template>
  <div>
    <h3>当前最新Count值为:{
    
    {
    
     count }}</h3>
    <button>-1</button>
  </div>
</template><script>
import {
    
     mapState } from "vuex";export default {
    
    
  computed: {
    
    
    ...mapState(["count"])
  }
};
</script>

Getter

  • Getters用于对Store中的数据进行加工处理形成新的数据,类似于Vue中的计算属性
  • Store中数据发生变化,Getters的数据也会跟随变化

使用方式一

在index.js中定义getter

//定义 Getter
const store = new Vuex.Store({
    
    
    state:{
    
    
    count: 0
    },
    getters:{
    
    
        showNum(state){
    
    
          return '当前Count值为:['+state.count']'
        }
      }
})

在组件中使用:

this.$store.getters.名称
<h3>{
    
    {
    
     this.$store.getters.showNum }}</h3>

使用方式二

在组件中,导入mapGetters函数

import {
    
     mapGetters } from 'vuex'

通过刚才导入的mapGetters函数,将需要的getters函数映射为当前组件的computed方法:

  computed: {
    
    
    ...mapGetters(["showNum"])
  }

使用时,直接调用即可:

<h3>{
    
    {
    
     showNum }}</h3>

Mutation

引入

如果想修改count的值,要怎么做呢?

也许聪明的你,已经想到,直接在组件中对this.$store.state.count进行操作即可,代码如下

<template>
  <div>
    <h3>当前最新Count值为:{
    
    {
    
    this.$store.state.count}}</h3>
    <button @click="add">+1</button>
  </div>
</template><script>
export default {
    
    
  methods: {
    
    
    add() {
    
    
      this.$store.state.count++;
    }
  }
};
</script>

测试发现,这可以实现需求,完成+1操作。

但是,这种方法在vuex中是严格禁止的,那要怎么做呢?这时,就需要使用Mutation了。
概念

Mutation用于变更存储在Store中的数据。

  • 只能通过mutation变更Store数据,不可以直接操作Store中的数据
  • 通过这种方式,虽然操作稍微繁琐一些,但可以集中监控所有数据的变化,直接操作Store数据是无法进行监控的

定义Mutation函数

在mutations中定义函数,如下:

mutations: {
    
        
      // 自增    
      add(state) {
    
          
      state.count++    
      }  
  }

定义的函数会有一个默认参数state,这个就是存储在Store中的state对象。
调用Mutation函数

Mutation中不可以执行异步操作,如需异步,请在Action中处理

方式一:
在组件中,通过this.$store.commit(方法名)完成触发

mutations: {
    
        
      // 自增    
      add(state) {
    
          
      state.count++    
      }  
  }

方式二:
在组件中导入mapMutations函数

import {
    
     mapMutations } from 'vuex'

通过刚才导入的mapMutations函数,将需要的mutations函数映射为当前组件的methods方法:

methods:{
    
    
    ...mapMutations('add','addN'),
    // 当前组件设置的click方法
    addCount(){
    
    
        this.add()
    }
}

Mutation传递参数

在通过mutation更新数据的时候,有时候需携带一些额外的参数,此处,参数被成为mutation的载荷Payload。

如果仅有一个参数时,那payload对应的就是这个参数值,eg.
在这里插入图片描述
如果是多参数的话,那就会以对象的形式传递,此时的payload是一个对象,可以从对象中取出相关的数据。

在mutations中定义函数时,同样可以接收参数,示例如下:

mutations: {
    
    
    // 自增
    add(state) {
    
    
      state.count++
    },
    // 带参数
    addNum(state, payload) {
    
    
      state.count += payload.number
    }
  }

在组件中,调用如下:

methods: {
    
    
  add() {
    
    
    //   this.$store.state.count++;
    this.$store.commit("add");
  },
  addNum() {
    
    
    this.$store.commit("addNum", {
    
    
      number: 10
    });
  }
}

Mutation响应规则

Vuex的store中的State是响应式的,当State中的数据发生改变时,Vue组件也会自动更新。

这就要求我们必须遵守一些Vuex对应的规则:

  • 提前在store中初始化好所需的属性
  • 当给 State 中的对象添加新属性时,使用如下方式:
    • 使用Vue.set(obj,‘newProp’,‘propValue’)
    • 用新对象给旧对象重新赋值
      示例代码:
updateUserInfo(state) {
    
    
      // 方式一
      Vue.set('user', 'address', '北京市')
      // 方式二
      state.user = {
    
    
        ...state.user,
        'address': '上海市'
      }
    }

Mutation常量类型

引入

思考一个问题:

在mutation中, 我们定义了很多事件类型(也就是其中的方法名称),当项目越来越大时,Vuex管理的状态越来越多,需要更新状态的情况也越来越多,也就意味着Mutation中的方法越来越多。

当方法过多,使用者需要花费大量时间精力去记住这些方法,甚至多个文件间来回切换,查看方法名称,也存在拷贝或拼写错误的情况。

那么该如何避免呢?

  • 在各种Flux实现中,一种很常见的方案就是使用常量替代Mutation事件的类型
  • 可以将这些常量放在一个单独的文件中,方便管理,整个App所有的事件类型一目了然

解决方案

  • 创建mutation-types.js文件,在其中定义常量
  • 定义常量时, 可以使用ES2015中的风格, 使用一个常量来作为函数的名称
  • 使用处引入文件即可

新建mutation-types.js:
在这里插入图片描述
在store/index.js中引入并使用:

import Vue from 'vue'
import Vuex from 'vuex'
import * as types from './mutation-type'

Vue.use(Vuex)

export default new Vuex.Store({
    
    
  state: {
    
    
    count: 0,
    user: {
    
    
      name: '旺财',
      age: 12
    }
  },
  mutations: {
    
    
    // 自增
    [types.ADD_NUM](state) {
    
    
      state.count++
    },
}

在组件中,引入并调用:

<script>
import {
    
     ADD_NUM } from "../store/mutation-type";
export default {
    
    
  methods: {
    
    
    add() {
    
    
      this.$store.commit(ADD_NUM);
      //   this.addAsync();
      //   this.$store.state.count++;
      //   this.$store.commit("add");
    }
  }
};
</script>

Action

Action类似于Mutation,但是是用于处理异步任务的,比如网络请求等

如果通过异步操作变更数据,必须通过Action,而不能使用Mutation,但在Action中还是要通过触发Mutation的方式间接变更数据。

参数context

在actions中定义的方法,都会有默认值context。

  • context是和store对象具有相同方法和属性的对象
  • 可以通过context进行commit相关操作,可以获取context.state数据

但他们并不是同一个对象,在Modules中会介绍到区别。

使用方式一

在index.js中,添加actions及对应的方法:

export default new Vuex.Store({
    
    
  state: {
    
    
    count: 0
  },
 //只有 mutations 中定义的函数,才有权力修改 state 中的数据
  mutations: {
    
    
    // 自增
    add(state) {
    
    
      state.count++
    }
  },
  actions: {
    
    
    addAsync(context) {
    
    
      setTimeout(() => {
    
    
      //在 action 中,不能直接修改 state 中的数据
      //必须通过 context.commit() 触发某个 mutation 才行
        context.commit('add')
      }, 1000);
    }
  }
})

在组件中调用:

<script>
export default {
    
    
  methods: {
    
    
    addNumSync(){
    
    
        // dispatch函数 专门用于触发 Action
        this.$store.dispatch('addAsync')
    }
  }
};
</script>

使用方式二

在组件中,导入mapActions函数

import {
    
     mapActions } from 'vuex'

通过刚才导入的mapActions函数,将需要的actions函数映射为当前组件的methods方法:

<script>
import {
    
     mapActions } from "vuex";
export default {
    
    
  methods: {
    
    
    ...mapActions(["addAsync"]),
    add() {
    
    Î
        this.addAsync()
    },
}

使用方式三

在导入mapActions后,可以直接将指定方法绑定在@click事件上。

...mapActions(["addAsync"]),
---------------------------
 <button @click="addAsync">+1(异步)</button>

该方式也适用于导入的mapMutations

Actions携带参数

在index.js的actions中,增加携带参数方法,如下:

export default new Vuex.Store({
    
    
  state: {
    
    
    count: 0
  },
  mutations: {
    
    
    // 带参数
    addNum(state, payload) {
    
    
      state.count += payload.number
    }
  },
  actions: {
    
    
    addAsyncParams(context, payload) {
    
    
      setTimeout(() => {
    
    
        context.commit('addNum', payload)
      }, 1000);
    }
  }
})

在组件中,调用如下:

methods: {
    
    
    addNumSyncParams() {
    
    
      this.$store.dispatch("addAsyncParams", {
    
    
        number: 100
      });
    }
  }

Actions与Promise结合

Promise经常用于异步操作,在Action中,可以将异步操作放在Promise中,并且在成功或失败后,调用对应的resolve或reject。

示例:

在store/index.js中,为actions添加异步方法:

actions: {
    
    
    loadUserInfo(context){
    
    
      return new Promise((resolve)=>{
    
    
        setTimeout(() => {
    
    
          context.commit('add')
          resolve()
        }, 2000);
      })
    }
  }

在组件中调用,如下:

methods: {
    
    
    addPromise() {
    
    
      this.$store.dispatch("loadUserInfo").then(res => {
    
    
        console.log("done");
      });
    }
}

Module

概念

Module是模块的意思,为什么会在Vuex中使用模块呢?

  • Vues使用单一状态树,意味着很多状态都会交给Vuex来管理
  • 当应用变的非常复杂时,Store对象就可能变的相当臃肿
  • 为解决这个问题,Vuex允许我们将store分割成模块(Module),并且每个模块拥有自己的State、Mutation、Actions、Getters等

使用

在store目录下,新建文件夹modules,用于存放各个模块的modules文件,此处以moduleA为例。

在modules文件夹中,新建moduleA.js,内部各属性state、mutations等都和之前一致,注释详见代码,示例如下:

export default {
    
    
    state: {
    
    
        name: '凤凰于飞'
    },
    actions: {
    
    
        aUpdateName(context) {
    
    
            setTimeout(() => {
    
    
                context.commit('updateName', '旺财')
            }, 1000);
        }
    },
    mutations: {
    
    
        updateName(state, payload) {
    
    
            state.name = payload
        }
    },
    getters: {
    
    
        fullName(state) {
    
    
            return state.name + '王昭君'
        },
        fullName2(state, getters) {
    
    
            // 通过getters调用本组方法
            return getters.fullName + ' 礼拜'
        },
        fullName3(state, getters, rootState) {
    
    
            // state代表当前module数据状态,rootState代表根节点数据状态
            return getters.fullName2 + rootState.counter
        }
    }
}
  • 局部状态通过context.state暴露出来,根节点状态则为context.rootState

在store/index.js中引用moduleA,如下:

import Vue from "vue"
import Vuex from "vuex"

import moduleA from './modules/moduleA'

Vue.use(Vuex)

const store = new Vuex.Store({
    
    
    modules: {
    
    
        a: moduleA
    }
})

export default store

这样就通过分模块完成了对状态管理的模块化拆分。

优化

如果项目非常复杂,除了分模块划分外,还可以将主模块的actions、mutations、getters等分别独立出去,拆分成单独的js文件,分别通过export导出,然后再index.js中导入使用。

示例: 分别将主模块的actions、mutations、getters独立成js文件并导出,以actions.js为例,

export default{
    
    
    aUpdateInfo(context, payload) {
    
    
        return new Promise((resolve, reject) => {
    
    
            setTimeout(() => {
    
    
                context.commit('updateInfo')
                resolve()
            }, 1000);
        })
    }
}

在store/index.js中,引入并使用,如下:

import Vue from "vue"
import Vuex from "vuex"
import mutations from './mutations'
import actions from './actions'
import getters from './getters'
import moduleA from './modules/moduleA'


Vue.use(Vuex)

const state = {
    
    
    counter: 1000,
    students: [
        {
    
     id: 1, name: '旺财', age: 12 },
        {
    
     id: 2, name: '小强', age: 31 },
        {
    
     id: 3, name: '大明', age: 45 },
        {
    
     id: 4, name: '狗蛋', age: 78 }
    ],
    info: {
    
    
        name: 'keko'
    }
}

const store = new Vuex.Store({
    
    
    state,
    mutations,
    getters,
    actions,
    modules: {
    
    
        a: moduleA
    }
})

export default store

最终项目目录图:
在这里插入图片描述

来源

手把手教你使用Vuex,猴子都能看懂的教程
Vue系列之—Vuex详解

猜你喜欢

转载自blog.csdn.net/weixin_44231544/article/details/132333567