一、概述
官方文档:vuex.vuejs.org/zh/
1. 什么是Vuex
什么是 Vuex ?官方的解释:Vuex
是一个专为 Vue.js
应用程序开发的==状态管理模式==。
- 它采用 ==集中式存储管理== 应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
- Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
状态管理模式、集中式存储管理这些名词听起来就非常高大上,让人捉摸不透。那么状态管理到底是什么?
其实,你可以简单的将其看成把需要多个组件共享的变量全部存储在一个对象里面。然后,将这个对象放在顶层的Vue实例中,让其他组件可以使用。那么,多个组件就可以共享这个对象中的所有变量属性了,并且这些变量属性是==响应式==的。
2. 状态管理
单界面的状态管理
单界面的状态管理,也就是指 vue 的响应式的状态原理
State
:不用多说,就是我们的状态。(你姑且可以当做就是data中的属性)View
:视图层,可以针对State的变化,显示不同的信息。Actions
:这里的Actions主要是用户的各种操作:点击、输入等等,会导致状态的改变。
多界面的状态管理
Vue 已经做好了单个界面的状态管理,但是如果是多个界面呢?多个视图都依赖同一个状态(一个状态改了,多个界面需要进行更新)
不同界面的 Actions 都想修改同一个状态,因此这些状态就不能只属于一个视图,需要一个大管家来统一帮助我们管理。Vuex就是为我们提供这个大管家的工具(全局单例模式)。
3. 什么状态需要管理
- 用户的登录状态、用户名称、头像、地理位置信息等等。
- 商品的收藏、购物车中的物品等等。
这些状态信息,我们都可以放在统一的地方,对它进行保存和管理,而且它们还是响应式的
二、State(状态)
Vuex 有几个比较核心的概念:
- State
- Getters
- Mutation
- Action
- Module
1. Vuex 入门
Vuex 的相关配置文件会放在 src/store
文件夹中。
-
先在
src/store
文件夹下创建一个index.js
文件,在其中 Vuex 的使用步骤与结构如下:// 1. 导入 import Vue from 'vue' import Vuex from 'vuex' // 2.安装插件 Vue.use(Vuex) // 3.创建对象并导出 export default new Vuex.Store({ state: { // 定义属性,用来存放共享的状态 count: 0 }, mutations: { // 定义方法,用来改变状态,通过 mutations 改变状态可以被 devtools 监视,方便调试 // 方法默认会有一个参数 state decrement(state) { state.count--; } }, actions: { }, modules: { } }) 复制代码
-
main.js 中将 Vuex 挂载到 Vue 实例,保证在所有的组件中都可以使用到
import Vue from 'vue' import App from './App' import store from './store' Vue.config.productionTip = false new Vue({ el: '#app', store, render: h => h(App) }) 复制代码
-
使用 Vuex 中共享的状态
- ==访问状态==:通过
this.$store.state.属性
- ==修改状态==:通过
this.$store.commit('mutation中方法')
2. State 单一状态树
Vuex 提出使用单一状态树,英文名称是Single Source of Truth
,也可以翻译成单一数据源。即数据都保存在一个 Store 对象中
如果你的状态信息是保存到多个Store对象中的,那么之后的管理和维护等等都会变得特别困难。
所以 Vuex 使用了单一状态树来管理应用层级的全部状态。单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便的管理和维护。
三、Getters(”计算属性”)
Getters 类似于 vue 中的计算属性 computed
-
第一个参数为
State
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) } } }) 复制代码
获取到 Getters 中的值:
{{$store.getters.doneTodos}} // vue 实例中这样获取: this.$store.getters.doneTodos 复制代码
-
第二个参数为
Getters
getters: { // ... doneTodosCount: (state, getters) => { return getters.doneTodos.length } } 复制代码
-
让 getter 返回一个函数,来实现给 getter 传参
getters: { getTodoById(state) { return function (id) { return state.todos.find(s => s.id === id) } } } // 简写1: getters: { getTodoById(state) { return id => { return state.todos.find(s => s.id === id) } } } // 简写2: getters: { getTodoById: (state) => (id) => { return state.todos.find(s => s.id === id) } } 复制代码
获取到 Getters 中的值:
{{$store.getters.getTodoById(2)}} // vue 实例中这样获取: this.$store.getters.doneTodos 复制代码
四、Mutations(同步更新状态)
1. 更新状态
官方原话:==更改 Vuex 的 store 中的状态的唯一方法是提交 mutation==。
每个 mutation
都有一个字符串的 ==事件类型== (type
) 和 一个 ==回调函数== (handler
)。
- 事件类型:就是指 mutation 中定义的函数的名字
- 回调函数:指函数的形参和函数体,回调函数第一个参数是
state
定义 mutations:
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
// 变更状态
state.count++
}
}
})
复制代码
状态更新:
this.$store.commit('increment')
复制代码
2. 传递参数(提交载荷(Payload))
在通过 mutation 更新数据的时候,有可能我们希望携带一些额外的参数
参数被称为是 mutation 的载荷(Payload)
// ...
mutations: {
increment (state, n) {
state.count += n
}
}
复制代码
this.$store.commit('increment', 10)
复制代码
在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读:
// ...
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
复制代码
this.$store.commit('increment', {
amount: 10
})
复制代码
3. 对象风格的提交方式
提交 mutation 的另一种方式是直接使用包含 type 属性的对象:
this.$store.commit({
type: 'increment',
amount: 10
})
复制代码
当使用对象风格的提交方式,整个对象都作为载荷传给 mutation 函数(包括 type 属性),因此 handler 保持不变:
mutations: {
// 上面提交的整个对象会传递给 payload
increment (state, payload) {
console.log(payload);
state.count += payload.amount
}
}
复制代码
4. 响应规则
Mutation 需要遵守 Vue 的响应规则
既然 Vuex 的 store 中的状态是响应式的,那么当我们变更状态时,监视状态的 Vue 组件也会自动更新。这也意味着 Vuex 中的 mutation 也需要与使用 Vue 一样遵守一些注意事项:
-
提前在你的 store 中初始化好所有所需属性。
-
当需要在对象上添加新属性的时候:
// 1. 使用 Vue.set Vue.set(obj, 'newProp', 123) // 2. 使用一个新的对象来替换旧的对象 state.obj = { ...state.obj, newProp: 123 } 复制代码
-
在对象上删除一个属性
Vue.delete(obj, 'oldProp') 复制代码
5. 使用常量替代 Mutation 事件类型
使用常量替代 mutation 事件类型在各种 Flux 实现中是很常见的模式。这样可以使 linter 之类的工具发挥作用,同时把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation 一目了然:
- 先定义一个常量,并导出
// 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) {
// mutate state
}
}
})
复制代码
- 提交的时候使用常量名
import { SOME_MUTATION } from './mutation-types'
this.$store.commit( SOME_MUTATION )
复制代码
6. Mutation 必须是同步函数
一条重要的原则就是要记住 ==mutation 必须是同步函数==。
每一条 mutation
被记录,devtools
都需要捕捉到前一状态和后一状态的快照。如果 mutation
中是异步函数,devtools
不知道什么时候回调函数实际上被调用,固将捕捉不到后一状态的快照。
因此,如果是异步操作,需要使用 Action
五、Actions(异步更新状态)
Action 类似于 mutation,不同在于:
Action
提交的是mutation
,而不是直接变更状态。Action
可以包含任意异步操作。
1. 定义
actions
中定义异步操作,定义的函数第一个形参默认是context
对象,这个对象是一个与store
实例具有相同方法和属性的对象context.commit()
:提交一个mutation
context.state
:获取state
context.getters
:获取getters
。- 第二个形参是载荷,与 mutation 中第二参数相同
- 在异步操作中如果要修改
state
中的状态,需要通过mutations
const store = new Vuex.Store({
state: {
counter: 1000,
},
mutations: {
decrement(state) {
state.counter--
}
},
actions: {
// context: 上下文
aDecrement(context, payload) {
setTimeout(() => {
console.log(payload);
context.commit('decrement')
}, 1000)
}
}
}
复制代码
2. 分发(dispatch)
在 Vue 组件中,通过 dispatch
来调用 action
中的方法
// 直接分发,不携带参数
this.$store.dispatch('aDecrement')
// 以载荷形式分发
this.$store.dispatch('aDecrement', {
amount: 10
})
// 以对象形式分发
this.$store.dispatch({
type: 'aDecrement',
amount: 10
})
复制代码
3. 返回 Promise
在 Action 中,我们可以将异步操作放在一个 Promise
中,并且在成功或者失败后,调用对应的 resolve 或 reject
actions: {
aDecrement(context, payload) {
return new Promise((resolve, reject) => {
setTimeout(() => {
context.commit('decrement');
console.log(payload);
resolve('可以传递参数')
}, 1000)
})
}
}
复制代码
调用这个方法:
this.$store
.dispatch('aDecrement', '我是携带的信息')
.then(res => {
console.log('里面完成了提交');
console.log(res);
})
复制代码
六、Module(模块化)
Module 是模块的意思,Vue 使用单一状态树,那么也意味着很多状态都会交给 Vuex 来管理。当应用变得非常复杂时,store对象就有可能变得相当臃肿。
为了解决这个问题,Vuex 允许我们将 store 分割成模块( Module ),而每个模块拥有自己的 state、mutations、actions、getters 等
1. 模块结构
- 模块中的
mutation
的第一个形参 state 是当前模块(局部)的 state - 模块中的
action
,局部状态通过context.state
暴露出来,根节点状态则为context.rootState
- 模块中的
getter
第一个形参 state 也是局部的 state,第三个形参rootState
是根节点的状态 - 通过
modules
将定义好的模块引入
const moduleA = {
state: () => ({
count: 0
}),
mutations: {
increment (state) {
// 这里的 `state` 对象是模块的局部状态
state.count++
}
},
getters: {
doubleCount (state, getters, rootState) {
return state.count * 2
}
},
actions: {
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
}
const moduleB = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
复制代码
2. 访问模块中的数据
-
访问模块中的 state,通过
store.state.模块名.局部状态名
{{$store.state.a.name}} 复制代码
-
访问模块中的 getters 、mutations、actions,直接访问,没有变化
$store.commit('increment') $store.getters.doubleCount $store.dispatch('incrementIfOddOnRootSum') 复制代码
3. 项目结构
Vuex 并不限制代码结构。但是,它规定了一些需要遵守的规则:
-
应用层级的状态应该集中到单个 store 对象中。
-
提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。
-
异步逻辑都应该封装到 action 里面。
├── index.html
├── main.js
├── api
│ └── ... # 抽取出API请求
├── components
│ ├── App.vue
│ └── ...
└── store
├── index.js # 我们组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
└── modules
├── cart.js # 购物车模块
└── products.js # 产品模块
复制代码
其它
1. 对象的解构
定义一个对象
const obj = {
id: '123',
name: '张三',
age: 23
}
复制代码
如果要获取这个对象中的属性,可以通过对象解构的方式:
// 根据变量名与对象中的属性名是否匹配来获取值
let {name, age} = obj;
复制代码
这种方式可以快速取出对象中需要的属性,注意变量名要与对象属性名相同。
如果形参传入一个对象,但是只需要其中的部分属性,可以这样写:
getObjParam({name, age}){
console.log(name);
}
复制代码