一. 概述
1 Vuex是什么
Vuex是一个集中式的状态管理插件, 实现所有组件的状态(数据)共享, 也是组件间通信的一种方式
- Vuex本质上是一个Vue的插件
- 主要实现所有组件的状态共享
2 什么时候用Vuex

一句话总结
官方推荐在中大型单页应用中使用
如何界定呢? 例如…….
- 需要与第三方的系统深度整合, 涉及异步获取数据, 大量使用全局变量, 业务逻辑比较复杂等情况时
- 如果超过3个组件需要
引用/修改同一个数据结构, 比如token, 用户收藏, 购物车中的商品 - 涉及到深层嵌套的组件需要与其它组件进行数据通讯
二. Vuex的工作原理
1 设计思路
Vuex可以理解成一个统一管理数据的仓库, 因此通常使用store表示
理论上, 所有的组件都可以读/写store中的数据
怎样实现读数据
可以定义一个单独的全局变量, 所有的组件从全局对象中获取数据
怎样实现写数据
如果在每个组件中各自修改全局变量, 就会引起混乱, 因此设计一个工具集中的管理写操作
如何设计集中式的写操作
所有的组件不能自己修改全局变量, 需要通知Vuex, 由Vuex集中管理, 统一修改
OK, 这样Vuex里至少应该包含两个部分
- state: 保存全局数据
- actions: 对数据进行修改的动作
因此, 对于简单的状态管理, 我们使用类似于下面的store模式就可以

Vuex在设计时, 将动作拆分成了两个部分
- Actions: 处理业务逻辑和异步任务
- Mutations: 对状态的修改

2 核心概念
1) Actions
动作, 行为
对象类型, 处理逻辑和异步操作
2) Mutations
变化, 转变
对象类型, 定义方法操作数据(同步)
3) State
状态, 数据
对象类型, 保存数据
三. 起步
1 创建vue项目
使用vue脚本创建项目vuex-demo
vue create vuex-demo
编写一个基本的计数器组件
<template><div><h1>点击了{{ count }}次</h1><button @click="increment">+1</button><button @click="decrement">-1</button><button @click="incrementIfOdd">是奇数就+1</button><button @click="incrementAsync">异步+1</button></div></template><script>export default {name: 'Counter',data() {return {count: 0,}},methods: {increment() {this.count++},decrement() {this.count--},incrementIfOdd() {if (this.count % 2) this.count++},incrementAsync() {setTimeout(() => {this.count++}, 500)},},}</script>
挂载到App.vue下
<template><div id="app"><counter></counter></div></template><script>import Counter from '@/components/Counter.vue'export default {name: 'App',components: {Counter,},}</script>
2 安装Vuex插件
使用npm安装Vuex
npm i vuex@3
:::tips 特别说明
一般在src目录下创建store/index.js
// 导入Vueimport Vue from 'vue'// 导入vueximport Vuex from 'vuex'// vuex本质上是vue的一个插件, 通过Vue.use()注册// 上所有的vue实例上挂载$storeVue.use(Vuex)// state: 状态const state = {}// mutations: 修改状态const mutations = {}// actions: 动作const actions = {}// 创建并导出store对象export default new Vuex.Store({state,mutations,actions,})
Store包含三个部分
- state: 对象类型, 保存数据
- mutations: 对象类型, 定义方法操作数据
- actions: 对象类型, 处理逻辑和异步操作
4 导入
在main.js中, 完成store对象的导入
import Vue from 'vue'import App from './App.vue'import store from './store'Vue.config.productionTip = falsenew Vue({store, // store: storerender: (h) => h(App),}).$mount('#app')
演示
四. 基本使用
为了熟悉Vuex的使用流程, 先了解单组件与Vuex如何交互
通过单组件熟悉语法和流程, 多组件使用也是类似的
1 完整流程
组件到Vuex的流程, 主要是写操作, 通过向Vuex发送通知, 让Vuex修改state中的数据
1) 流程图

关键API
- dispatch(分发, 派发): 从组件分发到Actions对象的一个方法
- commit(提交): 提交一个Mutations对象的一个方法
2) 从组件到Actions
dispatch的两个参数
- type: 对应action函数的名称
- payload(可选): 携带的数据
increment() {// 分发一个action, 对应调用名为increment的方法this.$store.dispatch('increment')},
发现报错

原因
在actions对象中, 并没有定义对应的increment方法
解决方法
在actions对象中定义increment方法
在increment方法中需要调用commit, 提交一个mutation, 所以这里设计了一个context参数保留了store大部分的方法, 但是出于性能考虑, 不太需要完整的store对象, 大家可以理解成一个简化版的store
increment(ctx, value) {console.log(ctx)},
通过观察, 我们发现这里也是可以拿到state的
思考
能否直接在actions方法中修改state
increment(ctx, value) {console.log(ctx)ctx.state.count += value},
理论上是可以的, 但是不推荐这么做, 原因后面细讲
在actions里, 最核心的工作是commit到Mutations
3) 从Actions到Mutations
increment(ctx, value) {ctx.commit('INCRMENT', value)},
小技巧
为了区分actions和mutations, 一般将mutations的名称大写
此时, 发现没有定义相关的mutation

解决
定义对应的mutations, 在方法中可以拿到state, 进而对state进行操作
INCREMENT(state, value) {console.log(state)state.count += value},
练习
完成decrement流程
完成incrementIfOdd流程
完成incrementAsync流程
参考答案
组件的改造
在组件中通过调用dispatch分发actions
methods: {increment() {// 分发一个action, 对应调用名为increment的方法this.$store.dispatch('increment', this.num)},decrement() {this.$store.dispatch('decrement', this.num)},incrementIfOdd() {this.$store.dispatch('incrementIfOdd', this.num)},incrementAsync() {this.$store.dispatch('incrementAsync', this.num)},},
Actions
const actions = {increment(ctx, value) {ctx.commit('INCREMENT', value)},decrement(ctx, value) {ctx.commit('DECREMENT', value)},incrementIfOdd(ctx, value) {if (ctx.state.count % 2) ctx.commit('INCREMENTIFODD', value)},incrementAsync(ctx, value) {setTimeout(() => {ctx.commit('INCREMENTASYNC', value)}, 500)},}
Mutations
const mutations = {INCREMENT(state, value) {console.log(state)state.count += value},DECREMENT(state, value) {state.count -= value},INCREMENTIFODD(state, value) {state.count += value},INCREMENTASYNC(state, value) {state.count += value},}
4) 从Vuex到组件
所有的组件对象都可以通过$store访问到Vuex, 进而可以拿到state
在组件中使用
<h1>计数总和: {{ $store.state.count }}</h1>
2 简化写流程
我们发现向increment和decrement这样的操作在actions没做什么逻辑操作, 直接转发给Mutations
所以, 我们思考是否可以跳过这个流程, 让组件直接commit到Mutations呢?
答案是肯定的, Vuex团队也注意到了这个问题
1) 流程图

2) 从组件直接到Mutations
示例
methods: {increment() {// 分发一个action, 对应调用名为increment的方法// this.$store.dispatch('increment', this.num)// 直接使用commit提交给Mutationsthis.$store.commit('INCREMENT', this.num)},decrement() {// this.$store.dispatch('decrement', this.num)// 直接使用commit提交给Mutationsthis.$store.commit('DECREMENT', this.num)},},
3 深入思考
1) 为什么将Actions和Mutations分开
Vuex设计的目的就是为中大型项目服务的
- Actions属于分层设计, 将复杂业务解耦, 承担了类似
中间件的角色 - Actions处理异步任务, Mutations处理同步任务
- Mutations方便调试与监控, 能更好地与
devtools集成
案例
在组件中, 分发一个complex任务到Actions
<button @click="$store.dispatch('complex')">复杂的逻辑</button>
在Actions中, 拆分成多个子任务, 完成解耦
complex(ctx) {console.log('处理复杂逻辑...')ctx.dispatch('sub1')},sub1(ctx) {console.log('处理子任务1...')ctx.dispatch('sub2')},sub2(ctx) {console.log('处理子任务2...')ctx.commit('INCREMENT', 1)},
子任务可以设计成异步任务, 从而提高效率, 而真正对数据的修改统一放到Mutations中完成
2) Vuex调试工具
由于所有的数据修改统一在Mutations中完成, 只需要监控Mutations就可以完全控制state的改变, 方便大型项目中的调试, 这也是为什么不推荐在Actions中直接修改state的原因
4 Getters配置
Vuex中的Getters类似于计算属性, 用于对数据的再次加工
需求
统计已完成的todo项目
1) 定义state
const state = {count: 0,todos: [{ id: 1, content: '待办1', done: true },{ id: 2, content: '待办2', done: false },{ id: 3, content: '待办3', done: false },],}
2) 定义getters
const getters = {doneTodos: (state) => {return state.todos.filter((todo) => todo.done)},}
3) 配置getters
// 创建并导出store对象export default new Vuex.Store({state,mutations,actions,getters,})
4) 在组件中调用
<h3>已完成的待办</h3><ul><li v-for="item in $store.getters.doneTodos" :key="item.id">{{ item.content }}</li></ul>
5) 其它用法
通过方法访问, 返回一个函数
<div>id为2的todo: {{ $store.getters.getTodoById(2) }}</div>
const getters = {doneTodos: (state) => {return state.todos.filter((todo) => todo.done)},getTodoById: (state) => {return (id) => {return state.todos.find((todo) => todo.id == id)}},}
五. map辅助函数
1 为什么需要辅助函数
在组件中访问state状态时, 每次都需要使用
$store.state.xxx
我们希望在使用的时候直接使用xxx该如何操作呢
可以在组件中定义计算属性
computed: {count() {return this.$store.state.count},name() {return this.$store.state.name},age() {return this.$store.state.age},},
我们发现computed里的函数差不多, 能否使用一个统一的写法
vuex团队也给提供了4个辅助函数
- mapState
- mapGetters
- mapMutations
- mapActions
2 mapState
为组件创建计算属性以返回 Vuex store 中的状态
先导入mapState等辅助函数
import { mapState } from 'vuex'
返回一个对象, 包含了计算属性函数
mounted() {// 返回一个对象console.log(mapState(['count', 'name', 'age']))},
...对象的展开语法
使用展开运算符...将对象展开完成方法的合并
computed: {// 使用对象的解构赋值...mapState(['count', 'name', 'age']),},
对象映射写法
如果希望计算属性名称和state状态名称不一致, 可以使用对象映射的写法
// mapState(对象写法)...mapState({ myCount: 'count', myName: 'name', myAge: 'age' }),
3 mapGetters
为组件创建计算属性以返回 getter 的返回值
同理, Vuex也提供了mapGetters辅助函数
import { mapState, mapGetters } from 'vuex'
在computed中
computed: {...mapGetters(['doneTodos', 'getTodoById']),}
4 mapMutations
创建组件方法分发 action
在methods中
...mapMutations({ increment: 'INCREMENT', decrement: 'DECRMENT' }),
注意
这里如果要传参, 只能在调用函数时传递
<button @click="increment(num)">+</button><button @click="decrement(num)">-</button>
5 mapActions
创建组件方法提交 mutation
在methods中
...mapActions(['incrementIfOdd', 'incrementAsync', 'complex']),
<button @click="incrementIfOdd(1)">是奇数就+1</button><button @click="incrementAsync(1)">异步+1</button>
<script>// 使用map辅助函数实现import { mapState, mapMutations, mapActions } from 'vuex'export default {name: 'Counter',data() {return {num: 0,}},computed: {// 对象的展开语法...mapState(['count']),},mounted() {// mapState返回一个对象/*count: function () {return this.$store.state.count},num: function() {return this.$store.state.num},age: function() {return this.$store.state.age}*/// console.log(mapState(['count', 'num', 'age']))},methods: {// increment() {// // count: 只是由本组件管理.// // 希望交给Vuex统一管理// // console.log(this.$store)// // 向Vuex分发一个action, 调用对应的actions中的方法// // this.$store.dispatch('increment')// this.$store.commit('INCREMENT')// },// decrement() {// // type: actions中的一个方法名字// // payload: 传递的数据(载荷)// // this.$store.dispatch('decrement', this.num)// this.$store.commit('DECREMENT', this.num)// },// incrementIfOdd() {// this.$store.dispatch('incrementIfOdd')// },// incrementAsync() {// this.$store.dispatch('incrementAsync')// },...mapMutations({ increment: 'INCREMENT', decrement: 'DECREMENT' }),// ...mapActions({// incrementIfOdd: 'incrementIfOdd',// incrementAsync: 'incrementAsync',// }),// 当key和value相同时, 推荐使用数组的写法...mapActions(['incrementIfOdd', 'incrementAsync']),},}</script>
