【Vue】前端状态管理之Vuex全解析

Vuex状态管理全解析

  • 一、状态管理
    • 1.1 状态管理是什么?
    • 1.2 为什么要用状态管理?
      • 1.2.1 生活中的例子
      • 1.2.2 代码中的例子
    • 1.3 三大框架的状态管理
  • 二、Vuex
    • 2.1 Vuex是什么?
    • 2.2 使用Vuex的好处
  • 三、Vuex基本使用
    • 3.1 安装依赖
    • 3.2 导入
    • 3.3 创建store对象
    • 3.4 挂载store对象
    • 3.5 实际使用
  • 四、Vuex核心概念
    • 4.0 store的基础代码示例
    • 4.1 State 状态
      • 4.1.1 概念
      • 4.1.2 State数据访问方式一
      • 4.1.3 State数据访问方式二
    • 4.2 Mutations 改变
      • 4.2.1 概念
      • 4.2.2 定义Mutations函数
      • 4.2.3 调用Mutations函数
        • 4.2.3.1 方式一
        • 4.2.3.2 方式二
      • 4.2.4 Mutation传递参数
      • 4.2.5 其他注意事项
    • 4.3 Actions 动作
      • 4.3.1 概念
      • 4.3.2 定义Actions 函数
      • 4.3.3 调用Actions 函数
        • 4.3.3.1 方式一
        • 4.3.3.2 方式二
        • 4.3.3.2 方式三
      • 4.3.4 Actions传递参数
      • 4.3.5 Actions与Promise结合
    • 4.4 Getters 获取
      • 4.4.1 概念
        • 4.4.2 使用方式一
        • 4.4.3 使用方式二
    • 4.5 Modules 模块
      • 4.5.1 概念
      • 4.5.2 使用
    • 4.6 优化

欢迎点赞收藏⭐留言如有错误敬请指正!

一、状态管理

1.1 状态管理是什么?

状态管理的概念主要是应用在单页应用SPA(Single Page Application)中的,是在React/Vue/Angular等现代化的前端框架流行起来之后才有的一个提法,之前的jQuery时代是没有这种概念的。
而所谓的"状态管理",简单来说其实就是对一个全局对象的一系列增删改查的操作。

1.2 为什么要用状态管理?

1.2.1 生活中的例子

图书馆里所有人都可以随意进书库借书还书,如果人数不多,这种方式可以提高效率减少流程,一旦人数多起来就容易混乱,书的走向不明确,甚至丢失。
所以需要一个图书管理员来专门记录借书的记录,也就是你要委托图书管理员给你借书及还书。

【Vue】前端状态管理之Vuex全解析_第1张图片

实际上,大多数状态管理方案都是如上思想,通过管理员(比如 Vuex)去规范书库里书本的借还(项目中需要存储的数据)。

1.2.2 代码中的例子

比如说个列表都引用了同一个数据源,用户在其中一张列表上做修改,另外一张列表上的数据也要跟着变。想要实现数据的同步更新,怎么办?

有人可能会说,这简单,用户在第一张表做修改的时候我们发一个消息给第二张表,让它把相应的数据改了不就完事了。但是如果有10张表,20张表呢?都去一张张通知吗?要是其他组件也有用到这个数据源,也都去通知更新吗?

所以这个方案在项目简单的时候或许可行,但是项目一复杂,问题立马就会暴露出来。为了以防未来项目推翻重来,我们还是应该一开始就考虑好。

总之状态管理这个概念,就是为了应对复杂的数据流动。

1.3 三大框架的状态管理

当今前端最火的三个框架要数Angular、Vue、React,它们全部都用到了组件化、模块化的思想,让前端开发变得更加灵活规范,但其中一个突出问题就是组件之间的状态管理问题。
组件化是好事,但是如果没有处理好组件之间的数据流动,就会造成项目混乱、维护困难的负面影响。

  • Vue:Vuex
  • React:Redux
  • Angular:Redux(ngrx)

二、Vuex

2.1 Vuex是什么?

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式
它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
官网链接_https://v3.vuex.vuejs.org/zh/
【Vue】前端状态管理之Vuex全解析_第2张图片

简单来说就是:Vue应用中遇到复杂的多组件共享状态时,使用Vuex。

2.2 使用Vuex的好处

  1. 能够集中管理共享的数据,易于开发与后期维护
  2. 能够高效的实现组件间数据共享
  3. 储存在Vuex中的数据都是响应式的,能够实时保持数据与页面同步

备注:
4. 父子组件间使用props就可以方便地实现状态共享,但嵌套多层的祖孙组件,又或者是兄弟组件使用props会很繁琐。
5. 使用事件总线可以解决(备注1)中的问题,但在事件状态数量很多时,会让代码的可读性变差(代码中有很多emiton我们比较难找到某个状态发生变更的时机),而且无法实现响应式的效果。
【Vue】前端状态管理之Vuex全解析_第3张图片
其他方式解析…

三、Vuex基本使用

Vuex的使用围绕着这张图实现:
【Vue】前端状态管理之Vuex全解析_第4张图片

3.1 安装依赖

npm install vuex --save

3.2 导入

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

3.3 创建store对象

const store = new Vuex.Store({
  // 存放全局共享的数据
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})

3.4 挂载store对象

new Vue({
  el: '#app',
  store,
})

3.5 实际使用

src/main.ts中

import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import { AxiosRequest } from "./utils/axios";
import { Component } from "vue-property-decorator";

Vue.use(AxiosRequest);

Vue.prototype.$message = message;
Vue.prototype.$event = new Vue(); // 事件总线

Component.registerHooks([
  "beforeRouteEnter",
  "beforeRouteLeave",
  "beforeRouteUpdate"
]);

new Vue({
  router,
  store,
  render: (h) => h(App),
}).$mount('#app');

四、Vuex核心概念

4.0 store的基础代码示例

以下示例讲解均基于此文件

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 0
  },
  // 只有 mutations 中定义的函数,才有权利修改 state 中的数据
  mutations: {
    add(state) {
      // 不要在 mutations 函数中,执行异步操作
      // setTimeout(() => {
      //   state.count++
      // }, 1000)
      state.count++
    },
    addN(state, step) {
      state.count += step
    },
    sub(state) {
      state.count--
    },
    subN(state, step) {
      state.count -= step
    }
  },
  actions: {
    addAsync(context) {
      setTimeout(() => {
        // 在 actions 中,不能直接修改 state 中的数据;
        // 必须通过 context.commit() 触发某个 mutation 才行
        context.commit('add')
      }, 1000)
    },
    addNAsync(context, step) {
      setTimeout(() => {
        context.commit('addN', step)
      }, 1000)
    },
    subAsync(context) {
      setTimeout(() => {
        context.commit('sub')
      }, 1000)
    },
    subNAsync(context, step) {
      setTimeout(() => {
        context.commit('subN', step)
      }, 1000)
    }
  },
  getters: {
    showNum(state) {
      return '当前最新的数量是【' + state.count + '】'
    }
  }
})

4.1 State 状态

4.1.1 概念

Vuex 使用单一状态树,即使用一个对象包含全部的应用层级状态。
单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便的管理和维护。

State就是唯一的公共数据源,所有共享的数据都要统一放到StoreState中进行存储。这也意味着,每个应用将仅仅包含一个 store 实例。

4.1.2 State数据访问方式一

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

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

4.1.3 State数据访问方式二

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

<template>
  <div>
    <h3>mapState---当前最新的count值为:{{count}}h3>
  div>
template>

<script>
import { mapState } from 'vuex'

export default {
  data() {
    return {}
  },
  computed: {
    ...mapState(['count'])
  }
}
script>

4.2 Mutations 改变

4.2.1 概念

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

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

4.2.2 定义Mutations函数

mutations中定义函数,如下:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 0
  },
  // 只有 mutations 中定义的函数,才有权利修改 state 中的数据
  mutations: {
    add(state) {
      state.count++
  }
})

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

4.2.3 调用Mutations函数

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

4.2.3.1 方式一

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

export default {
  methods: {
    add() {
      // this.$store.state.count++; 错误写法
      this.$store.commit("add");
    }
  }
};
4.2.3.2 方式二

在组件中导入mapMutations函数

import { mapMutations } from 'vuex'

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

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

4.2.4 Mutation传递参数

在通过mutations更新数据的时候,有时候需携带一些额外的参数,此处参数被称为mutations的载荷payload

  1. 如果仅有一个参数时,那payload对应的就是这个参数值,例如:
// mutations内的函数
addN(state, step) {
  state.count += step
}
// methods
btnHandler2() {
  // commit 的作用,就是调用 某个 mutations 函数
  this.$store.commit('addN', 3)
}
  1. 如果是多参数的话,那就会以对象的形式传递,此时的payload是一个对象,可以从对象中取出相关的数据,例如:
// mutations内的函数
addNum(state, payload) {
  state.count += payload.number
}
// methods
addNum() {
  this.$store.commit("addNum", {
    number: 10,
    step: 1
  });
}

备注:在大多数情况下,载荷payload应该是一个对象,这样可以包含多个字段并且记录的 mutations 会更易读。对象风格的提交方式

4.2.5 其他注意事项

  1. Mutation 必须是同步函数
  2. Mutation 需遵守 Vue 的响应规则
  3. 可以使用常量替代 Mutation 事件类型

4.3 Actions 动作

4.3.1 概念

Actions类似于Mutations,但是是用于处理异步任务的,比如网络请求等。
如果通过异步操作变更数据,必须通过Actions,而不能使用Mutations,但在Actions中还是要通过触发Mutations的方式间接变更数据。

  • Actions 提交的是 mutations,而不是直接变更状态。
  • Actions 可以包含任意异步操作。

4.3.2 定义Actions 函数

actions中定义函数,如下:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

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

actions中定义的方法,都会有一个默认参数context

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

4.3.3 调用Actions 函数

4.3.3.1 方式一

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

export default new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    // 自增
    add(state) {
      state.count++
    }
  },
  actions: {
    addAsync(context) {
      setTimeout(() => {
        context.commit('add')
      }, 1000);
    }
  }
})

组件中调用:

<script>
export default {
  methods: {
    addNumSync(){
        // dispatch用于触发Actions中的方法
        this.$store.dispatch('addAsync')
    }
  }
};
</script>

4.3.3.2 方式二

在组件中,导入mapActions函数:

import { mapActions } from 'vuex'

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

import { mapActions } from "vuex";
export default {
  methods: {
    ...mapActions(["addAsync"]),
    add() {
        this.addAsync()
    },
}
4.3.3.2 方式三

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

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

该方式也适用于导入的mapMutations

4.3.4 Actions传递参数

index.jsactions中,增加携带参数方法,如下:

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
      });
    }
}

4.3.5 Actions与Promise结合

Promise经常用于异步操作,在Actions中,可以将异步操作放在Promise中,并且在成功或失败后,调用对应的resolvereject
示例:

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");
      });
    }
}

4.4 Getters 获取

4.4.1 概念

  • Getters用于对Store中的数据进行加工处理形成新的数据,类似于Vue中的计算属性,getters 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
  • Store中数据发生变化,Getters的数据也会跟随变化
4.4.2 使用方式一

index.js中定义getters

getters:{
  showNum(state){
    return '当前Count值为:'+state.count
  }
}

在组件中使用:

<h3>{{ this.$store.getters.showNum }}h3>
4.4.3 使用方式二

在组件中,导入mapGetters函数

import { mapGetters } from 'vuex'

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

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

使用时,直接调用即可:

<h3>{{ showNum }}h3>

4.5 Modules 模块

4.5.1 概念

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

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

4.5.2 使用

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

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

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 + '1号'
        },
        fullName2(state, getters) {
            // 通过getters调用本组方法
            return getters.fullName + '2号'
        },
        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

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

4.6 优化

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

示例:
分别将主模块的actionsmutationsgetters独立成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'   // 引入的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,   // 引入的actions
    modules: {
        a: moduleA
    }
})

export default store

最终项目目录图:
结构清晰明了,也便于后期的维护
【Vue】前端状态管理之Vuex全解析_第5张图片

欢迎点赞收藏⭐留言如有错误敬请指正!

单向数据流
详解vuex
B站vuex视频教程
与ts结合使用—vuex-module-decorators

你可能感兴趣的:(vue,vue.js,vue,状态管理,vuex)