四、Vuex
ps:仅供本人记忆
4.1 认识Vuex
4.1.1 什么是Vuex?
- Vuex是一个状态管理模式。
- 他可以将多个组件间需要共享的数据(状态),保存在一个容器中(store)。
- 每个组件可以直接获取和修改这些共享的数据(状态)
- Vuex 的状态存储是响应式的,若 容器(store) 中的数据发生变化,则会重新渲染对应页面视图
- 有哪些数据需要在多个组件间共享?
- 用户登录状态、用户名称、头像等等
- Vuex学习文档:https://vuex.vuejs.org/zh/guide/actions.html
4.1.2 单组件状态管理
- 在单个组件中进行状态管理是一件非常简单的事情
- 每个组件有属于自己的数据(data)、行为(methods)、视图(html模板)
- data往往是通过methods进行改变,data改变后其组件的视图也会发生更新
- 可以通过计时器案例加深对这张图的理解
4.1.3 多组件状态管理
- 多页面状态管理核心:多个视图都依赖同一个状态(数据)(一个状态改了,多个界面需要进行更新)
- Vuex就是为我们提供存储多个共享状态的工具。
- 全局单例模式(大管家)
- 我们现在要做的就是将共享的状态抽取出来,交给Vuex统一进行管理。
- 之后每个组件,按照Vuex制定的规范,进行访问和修改等操作。
- 这就是Vuex背后的基本思想。
- Vuex状态管理图例
4.1.4 Vuex的基本使用
- 通过vue-cli搭建项目时,选择vuex包,或者自己手动下载vuex包
- 在src目录下,新建一个store文件夹,里面新建一个index.js文件,用于存放Vuex相关代码
- 编写Vuex相关代码
- 在项目的入口JS文件中,导入Vuex实例,并写入Vue的配置选项中
-
- 将Vuex实例挂载到Vue的配置选项上时,其他所有组件的$store其实就是入口JS文件中导入的store
- 在其他组件中,可以通过this.$store的方式,获取到这个store对象了
-
- 上图中methods下的方法的return可以去掉
- 注意事项:Vuex并不允许直接在组件中改变共享的数据,而是要通过提交mutation的方式来修改共享的数据
- 只有这样,谷歌浏览器插件Vue.js devtools才能够实时记录共享数据的改变
4.2 Vue核心概念
- Vuex有几个比较核心的概念,而这几个核心概念正是创建Vuex实例时,所要传入的配置对象
-
- // 创建Vuex实例
const store = new Vuex.Store({
state: {},
getters: {},
mutations: {},
actions: {},
modules: {}
})
- // 创建Vuex实例
4.2.1 State
- 类型:Object
- 详细:
- 用于定义Vuex中一些共享的状态(数据),其他组件可以通过this.$store.state.xxx来获取共享的数据
- 示例:
-
- const store = new Vuex.Store({
state: {
count: 0
}
}
// 其他组件可以访问this.$store.state.count数据
- const store = new Vuex.Store({
4.2.2 Getters
- 类型:{ [key: string]: Function }
- 详细:
- Getters类似于组件的计算属性,用于对state中的数据进行加工过滤操作,所有组件都可以通过this.$store.getters.xxx访问getters下所有定义的属性
- Getters中的内部方法可以接受两个参数
- 第一个参数为state,可以直接通过state.xxx获取定义在state下的属性
- 第二个参数为getters,可以通过getters.xxx获取定义在getters下的属性
- Getters中的内部方法,必须要有返回值
- 虽然Getters内部是写的方法,但是Vuex会将他内部的方法解析为store.getters下的属性
- 示例:
- 如果希望getters下定义的方法能够接受参数,则要使用如下写法
辅助函数
- mapGetters 辅助函数仅仅是将 store 中的 getter 映射到组件的计算属性中
-
- 上图代码中,将Vuex -> getters -> cartLength 计算属性映射为组件中的 this.cartLength
- 如果你想将一个 getter 属性另取一个名字,使用对象形式:
-
- ...mapGetters({
// 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
doneCount: 'doneTodosCount'
})
- ...mapGetters({
- 除了mapGetters辅助函数,Vuex还提供了mapActions、mapState辅助函数,用法一致,都是采用映射原理!
4.2.3 Mutations
1. mutations基本使用
- 类型:{ [key: string]: Function }
- 详细:
- mutations里主要定义一些方法,用于修改Vuex中store状态(数据)。
- mutations中的内部方法会接受state 作为第一个参数,可以通过state.xxx获取store数据
- 当其他组件想要修改修改Vuex中store状态(数据)时
- Vuex不允许在组件中直接调用this.$store.mutations.xxx()方式来修改store状态
- 必须通过this.$store.commit('mutations下的方法名', 要传递的参数)来执行mutations下对应的方法修改数据
- 示例:
-
- 上图中methods下的return可以去掉
2. mutations其他提交方式
- 提交mutations的另外一种风格
- 除了this.$store.commit('xxx')可以提交mutations之外,Vuex还为我们提供了另外一种提交方式
-
- 这种提交风格比较少用
3. mutations响应规则
- 当我们使用mutations更新store中的数据时,必须遵守Vuex对应的一些规范
- mutations更新数据规范
- 最好提前在你的 store --> state 中初始化好所有所需属性
- 当需要在对象上添加新属性时,应该使用Vue.set(obj, 'newProp', value)方式更新数据
4. mutations常量(了解)
- 当mutations中方法过多时, 使用者需要花费大量的经历去记住这些方法名,甚至是多个文件间来回切换, 查看方法名称
- Vuex允许我们可以创建一个文件: mutation-types.js,并且在其中定义mutations中方法名的常量,然后将其导出
- 导入定义的常量时, 我们可以使用ES2015中的风格,使用一个常量来作为函数的名称
- mutation-types.js
-
- export const SOME_MUTATION = 'SOME_MUTATION'
- store.js
-
- import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'
const store = new Vuex.Store({
state: { ... },
mutations: {
// 我们可以使用 ES2015 风格的命名功能来使用一个常量作为函数名
[SOME_MUTATION] (state) {
// ...
}
}
})
- import Vuex from 'vuex'
- App.vue
-
- import * as type from './store/mutation-types'
export default {
methods: {
add(number) {
this.$store.commit(type.SOME_MUTATION)
}
}
}
- import * as type from './store/mutation-types'
- Vuex官网说道:
- 用不用常量取决于你
- 但是在需要多人协作的大型项目中,这会很有帮助。但如果你不喜欢,你完全可以不这样做。
5. mutations必须是同步函数
- Vuex要求我们Mutation中的方法必须是同步方法.
- 如果Mutations中的方式是一个异步操作,则在Vue devtools中将会无法记录到更新后的状态
- 上图中你会发现state中的count数据一直没有被改变,因为vue devtools无法追踪到
- So, 不要再mutation中进行异步的操作
4.2.4 Actions
1. actions的理解
- Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。Action 可以包含任意异步操作
- 先回顾一下Vuex的状态管理图
- Vuex不允许我们在mutations中进行异步操作,允许我们将异步相关操作写在actions中
- 但是!直接修改数据的操作还是要在mutations中定义,实际上actions中只是存放异步操作,并且在异步操作中通过commit方式提交mutations,完成异步操作数据的更新
- 说起来有点难以理解,接下来我们看代码的展示!
2. actions的基本使用
- 类型:{ [key: string]: Function }
- 详细:
- actions里主要定义一些方法,用于存放异步相关操作
- actions中的内部方法会接受context 作为第一个参数。context是和store对象具有相同方法和属性的对象。
- 因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters
- 当我们在之后介绍到 Modules 时,你就知道 context 对象为什么不是 store 实例本身了
- 组件中可以通过this.$store.dispatch('actions下的方法名'),执行对应的actions方法
- 示例:
3. actions传参
- 组件在调用actions中的方法时,同样支持传递参数
4. 解构context形参
- 我们会经常用到 ES2015 的 参数解构 来简化代码(特别是我们需要调用 commit 很多次的时候):
-
- actions: {
adelay({ commit, getters, state }, payload) {
setTimeout(() => {
commit('delay', payload)
}, 2000);
}
},
- actions: {
5. actions返回Promise
- 当我们通过actions执行异步操作时,我们希望能够知道这个异步操作什么时候结束。
- 首先,你需要明白 store.dispatch('xxx'),将会调用actions下的处理函数,因此我们可以让其处理函数返回一个Promise实例
- 在Action中, 我们可以将异步操作放在一个Promise中, 并且调用对应的resolve或reject
4.2.5 Module (了解)
1. 认识Module
- Vuex允许我们将store分割成模块, 而每个模块拥有自己的state、mutations、actions、getters等
- 每个模块拥有自己的局部状态(数据)。与store定义的全局状态(数据)不会冲突
2. 模块命名空间
- 默认情况下,模块内部的 action、mutation 和 getter 是注册在store下的
- 这意味着模块内部的方法名与store中的方法名不能冲突
- 你可以通过添加 namespaced: true 属性的方式使其成为带命名空间的模块。开启命名空间后,组件需要通过特定的语法去访问mutations、actions、以及getters
- 组件访问模块的state:this.$store.模块.属性名
- 组件访问模块的getters:this.$store.getters['模块名/属性名']
- 组件提交模块的mutations:this.$store.commit('模块名/方法名')
- 组件提交模块的actions:this.$store.dispatch('模块名/方法名')
3. 模块的局部状态
- 对于模块内部的 mutations 和 getters,接收的第一个参数是模块的局部状态对象。
- 对于模块内部的 actions,局部状态通过 context.state 暴露出来,全局状态则为 context.rootState,对于模块内部的 getter,全局状态会作为第三个参数暴露出来
-
- const moduleA = {
actions: {
add ({ state, commit, rootState }) {
// 这里的state为局部状态,rootState为全局状态
commit('increment')
},
getters: {
moduleAbar(state, getters, rootState) {
// 这里的 `state`是模块的局部状态 `rootState`是全局状态
return state.name + 111
}
},
}
}
- const moduleA = {
4.3 Vuex项目结构划分
- Vuex的相关代码我们都是存放在store文件夹下的index.js中
- 当index.js文件代码量太多时,只需将 actions、mutations 和 getters 分割到单独的JS文件。并用模块化导入到index.js中,这样的代码更好维护