前言
写在开篇
看到身边的大佬一个二个的秀到飞起,我也要加把劲才是
更新日志
2021/11/01
写下这篇博文,因为难以解决的bug被劝退到自闭
2022/02/13
遇到了一个好的机会,所以又重新拾起这部分内容,更棒的是有仙人指路一下子豁然开朗起来
特别鸣谢
感谢19级软件工程朱珂江学长凌晨的技术指导,我这才得以顺利入门
概览
什么是Vuex
Vuex是一个专为Vue.js应用开发的状态管理模式 和 库(与Vue 3.x对应的是Vuex 4),
采用集中式存储管理应用的所有组件的状态,并且已响应的规则来保证状态以一种可以预测的方式发生变化。
Vuex集成到Vue的官方调试工具devtools extension,提供了诸如零件配置的time-travel调试、状态快照导入导出等高级调试功能
单向数据流的弊端
由于vue提倡的是单向数据流,如果组件与组件层层嵌套,那么利用父子间通信的方式进行数据传递就相当麻烦(其实也可以通过 发布和订阅 来解决这种问题)
在制定出解决这个问题的方案之前,我们先了解一下单向数据流中的几个部分:
状态
驱动应用的数据源
视图
以声明的方式将状态映射到视图
操作
响应在视图上的用户输入导致的状态变化
它们之间的关系像是这样
Vuex状态管理模式
vuex就相当于给所有需要数据通信的组件创建一个公共的父级,这样进行数据传递的话就快多了
(但是使用Vuex的成本比较高,所以只有中大型项目才使用)
“store”基本上就是一个容器,它包含着你的应用中大部分的**状态 (state)**。Vuex 和单纯的全局对象有以下两点不同:
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
- 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
什么时候使用Vuex
如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。
上面的是官方文档中的介绍,下面的是某大佬的个人理解:
管你小中大型应用,我就想用就用呗,一个长期构建的小型应用项目,谁能知道项目需求以后会是什么样子,毕竟在这浮躁的时代,需求就跟川剧变脸一样快,对不对?毕竟学习了 Vuex 不立马用到项目实战中,你永远不可能揭开 Vuex 的面纱。项目中使用多了,自然而然就会知道什么时候该用上状态管理,什么时候不需要。老话说的好熟能生巧,你认为呢?
(括弧 – 先了解好Vuex 一些基本概念,然后在自己的项目中使用过后,再用到你公司项目上,你别这么虎一上来就给用上去了~)
简单使用
安装版本问题
如果你仔细阅读了本博客的更新日志,你会发现第一次更新和第二次更新之间相差了近3个月,而这是因为一个当时未得到解决的bug劝退了我,所以停止了学习的步伐
但是最近由于一个项目特别适合用vuex,我便觉得这该是一个非常好的机会啊,就重新拾起了学习vuex的热情
使用如下npm命令安装:
npm install vuex --save
但是在我代码没有问题的前提下,这个bug依旧出现:
我也因此在这里又卡顿了两三天,后来经过朱老师的点拨,才终于豁然开朗了
原来这是因为Vue和VueX的版本的关系,我们这里用的是Vue2和VueX4.x,版本不匹配所以导致了报错
所以此时正确的操作应该是:
# 直接npm install vuex而不指定版本的话就是安装最新版(4.x),
# 版本指定第一位即可
npm install vue@3 --save
正确的版本对应关系如下:
Vue版本 | VueX版本 |
---|---|
Vue2.x | VueX3.x |
Vue3.x | VueX4.x |
Vue3.x(猜测,暂未查证) | Pinia(可以看做VueX5.x) |
这里个人习惯使用Vue2.x,所以下文如未特殊说明,皆为Vue2.x
基本文件结构
|—— index.html
|—— main.js
|—— api
| |____ …
|—— components
| |____ App.vue
| |____ …
|—— store # 核心文件夹
|____ index.js # 组装导出模块
|____ action.js # 根级action
|____ mutations.js # 根级mutation
|____ modules # 各种模块
|____ ….
上述文件结构是针对于大型应用的标准文件结构,这里我们只是入门,所以并不严格遵循,而是将所有js文件糅合成一个store.js,并且暂时先不做模块化
然后在store.js和main.js中分别写入:
Vuex 通过 store
选项,提供了一种机制将状态从根组件“注入”到每一个子组件中(需调用 Vue.use(Vuex)
)
通过在根实例中注册 store
选项,该 store 实例会注入到根组件下的所有子组件中,且子组件能通过 this.$store
访问到
基本数据使用
在进行了上述操作之后,如果想要使用数据msg我们只需要使用如下语句:
this.$store.state.msg
// 这里的this在template部分中可以省略
// 但是在JS部分中是不可以省略的
使用数据并不困难,我们需要注意的是如何修改数据,我们虽然可以通过对上述语句赋值的形式
来修改,但是我们并不推荐这样做,因为这些数据应该被视为private,但是我们又希望所有组件都可以访问并修改它们,这时候就该想到getter和setter
getter我们会在后面提到
这里的setter被放到mutations里面,而要访问到这些setter则需要用this.$store.commit,例如:
基本数据修改
不建议直接使用this.$store.state
直接修改,一是因为这样修改后没有任何提示,若出错则可能难以追溯问题来源;二是因为这样修改则形成了双向绑定结构,对于数据不安全;
所以推荐像下面这样:
这里我们可以发现,mutation
中的函数的第一个参数是state,这个是固定的,而如果需要更多的参数则在后面依次添加,传参只需要像这样:
this.$store.commit('change', '我是参数')
注意这里如果要追加参数,不能用逗号隔开来继续添加,而是将参数打包成一个对象传入——当然,函数声明也要遵循这一点
另外,还要注意,mutation只能是同步任务
如何异步后面会提到
基本数据增删
并不需要使用额外的语法,只需要我们像下面这样就能完成
由于新添加的属性如果要规范使用的话还得添加额外的mapState
语句,所以我们会再包装一层:
this.$store.state.obj = { ...this.$store.obj, new: 233 }
或者这样写也行:
Vue.set( state.obj, 'new', 123 )
归根结底commit
是触发mutation
中函数的方式之一,我们还能通过mapMutations
来触发,不过这里暂时不讨论这个
核心概念
在学会基础使用之后,再来稍微深入一点,接下来这部分内容省略了已经提及过的部分
State
单一状态树
用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源 (SSOT (opens new window))”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段
组件中获取 Vuex 状态
那么我们如何在 Vue 组件中展示状态呢?由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性 (opens new window)中返回某个状态:
// 创建一个 Counter 组件
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return store.state.count
}
}
}
每当 store.state.count
变化的时候, 都会重新求取计算属性,并且触发更新相关联的 DOM
相较于直接使用store.state.count访问,更推荐使用
mapState 辅助函数
当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余
为了解决这个问题,我们可以使用 mapState
辅助函数帮助我们生成计算属性
当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState
传一个字符串数组
import { mapState } from 'vuex'
export default {
// ...
computed: {
...mapState(['msg'])
}
}
在这样操作之后,你就能直接使用msg
来访问变量,而不是$store.state.msg
mapState的原理就是字符串映射,将msg映射成为一个与之相关的函数,并放入一个对象返回
请看下面的例子:
Mutations
同步原则
mutation
必须是同步函数,
举个例子:
devtool在debug过程中,需要记录mutation的前一状态和后一状态,
但是在mutation执行的时候,异步操作还未执行,这就使得状态无法跟踪,
但是这种操作在视图层渲染上却是没有问题的——可正是这样,又导致了数据发生了不同步
那么异步的操作要到哪里执行呢?我们后面再提
mapMutations辅助函数
和mapState类似,mupMutation
作为触发mutation
内的函数的第二种方式
有两种使用方式,具体语法如下:
Actions
处理异步
Action是专门用于处理任务的
如果通过异步操作更改数据,必须通过action
,而不能使用mutation
,但是在action
中还是要通过触发mutation
的方式间接变更数据——只有mutation
才能修改state
也就是:
其中的context可以理解为new Vuex.Store实例对象
这里要注意,actions中的函数不是用commit触发了,而是用dispatch
this.$store.dispatch('plusAsync')
如果需要传参,那么也是和commit
一个道理
如果是同步的内容,那么可以用dispatch或commit,异步只能dispatch
mapActions辅助函数
dispatch是第一种方式,而第二种方式是mapActions
方法跟之前的辅助函数如出一辙,此处不再赘述
Getters
Getter是用于对Store中的数据进行加工处理,然后形成新的数据(深拷贝),并不修改原本的数据
可以认为是 store 的计算属性,就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算
computed: {
doneTodosCount () {
return this.$store.state.todos.filter(todo => todo.done).length
}
}
如果有多个组件需要用到此属性,我们要么复制这个函数,或者抽取到一个共享函数然后在多处导入它——无论哪种方式都不是很理想
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
})
通过属性访问
Getter 会暴露为 store.getters
对象,你可以以属性的形式访问这些值:
store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]
注意,getter 在通过属性访问时是作为 Vue 的响应式系统的一部分缓存其中的。
通过方法访问
你也可以通过让 getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时非常有用。
(这个方法真的是震撼我一年)
getters: {
// ...
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
注意,getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果。
mapGetters辅助函数
依然是那么回事,只不过说mapGetters
一般只能放到computed中
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}
Modules
由于是Vuex是单一状态树,所以当状态过多的时候,store对象就会相当臃肿,为了解决这种问题,就提出了将store分割成多个模块的思想
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
基本使用就是向上面这样,更多深入的地方以后再进行学习
参考资料
Junting的博文:https://www.jianshu.com/p/c6356958ca52
Vuex官方文档:https://vuex.vuejs.org/zh/guide/mutations.htm
Vuex从入门到实战:https://www.bilibili.com/video/BV1h7411N7bg