1、Vuex是什么?
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。
它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
什么是状态管理模式?
下面是一个简单的 Vue 计数应用:
<template>
<div id="app">
<h2>{{count}}</h2>
<button @click="count--">-</button>
<button @click="count++">+</button>
</div>
</template>
这个状态自管理应用包含以下几个部分:
- state,组件的数据(count);
- view,以声明方式将状态显示到视图(将count显示到网页);
- actions,用户的操作导致的状态变化(-和+按钮)。
以上就是一个表示“单向数据流”的例子,简单示意图如下:
但是,当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:
- 多个视图依赖于同一状态。
- 来自不同视图的行为需要变更同一状态。
这时候,我们就需要一个“管家”来管理多个组件之间共享的状态。
当然我们可以自己封装一个对象来管理,但是Vuex提供的最大的便利就是响应式——当数据发生变化时,view能够实时的发生变化。
2、安装
NPM
npm install vuex --save
3、开始
每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
- 你不要直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得
Devtools
方便地跟踪每一个状态的变化,从而能够更好的了解自己的应用。
最简单的Store
index.js
const store = new Vuex.Store({
state: {
count: 1000
},
mutations: {
dedcrement(state) {
state.count--
},
increment (state) {
state.count++
}
}
})
App.vue
<template>
<div id="app">
<h2>{{$store.state.count}}</h2>
<button @click="$store.commit('dedcrement')">-</button>
<button @click="$store.commit('increment')">+</button>
</div>
</template>
4、核心概念
1)、State
单一状态树
Vuex 使用单一状态树——用一个对象就包含了全部的状态。使它作为唯一的数据源而存在。
2)、Getter
getter
可以认为是 store 的计算属性。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
Getter 接受 state 作为其第一个参数:
index.js
const store = new Vuex.Store({
state: {
student: [
{id:1, name: 'yanjundong', age: 18},
{id:2, name: 'wangwu', age: 36},
{id:3, name: 'li', age: 10},
{id:4, name: 'zhangsan', age: 24}
]
},
getters:{
greaterAgeStus: state => {
return state.student.filter(s => s.age >= 20)
}
},
})
通过属性访问
Getter 会暴露为 store.getters
对象,你可以以属性的形式访问这些值:
$store.getters.greaterAgeStus // -> [ { "id": 2, "name": "wangwu", "age": 36 }, { "id": 4, "name": "zhangsan", "age": 24 } ]
Getter 也可以接受其他 getter 作为第二个参数:
const store = new Vuex.Store({
state: {
...
},
getters:{
...
greaterAgeCount: (state, getters) => {
return getters.greaterAgeStus.length
}
},
})
$store.getters.greaterAgeCount // -> 2
注意,getter 在通过属性访问时是作为 Vue 的响应式系统的一部分缓存其中的。
通过方法访问
你也可以通过让 getter 返回一个函数,来实现给 getter 传参。
getters:{
...
findByAge: state => {
return (age) => {
return state.student.filter(s => s.age >= age)
}
}
},
$store.getters.findByAge(25) // -> [ { "id": 2, "name": "wangwu", "age": 36 } ]
3)、Mutation
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。
以上的开始中已经有了Mutation的简单应用
提交载荷(Payload)——携带参数
你可以向 store.commit
传入额外的参数,即 mutation 的 载荷(payload):
// ...
mutations: {
increment (state, n) {
state.count += n
}
}
store.commit('increment', 10)
在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读。
// ...
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
store.commit('increment', {
amount: 10
})
另外一种提交方式
直接使用包含 type
属性的对象:
store.commit({
type: 'increment',
amount: 10
})
当使用对象风格的提交方式,整个的对象都作为参数传给mutations函数
mutations: {
increment (state, payload) { //payload: {type: "increment", count: 10}
state.count += payload.amount
}
}
Mutation 需遵守 Vue 的响应规则
Vuex 的 store 中的状态是响应式的,当我们变更状态时,监视状态的 Vue 组件也会自动更新。
因此我们需要遵守Vue的一些规则:
-
最好提前在 store 中初始化好所有所需属性。
-
当需要给对象上添加新属性时,应该
- 使用
Vue.set(obj, 'newProp', 123)
- 用新对象给旧对象重新赋值
state: { ... info: { name: 'iphone', version: '8plus', storage: '64G' } }, mutations: { updateInfo(state) { //state.info.storage = '128G'; //响应式 //state.info.price = '¥4300'; //做不到响应式 Vue.set(state.info, 'price', '¥4300'); //响应式 //delete state.info.storage; //不能响应 //Vue.delete(state.info, 'storage'); //响应式 } }
- 使用
使用常量替代 Mutation 事件类型
使用常量替代 mutation 事件类型,同时把这些常量放在单独的文件中可以让你的代码合作者对整个 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
}
}
})
//App.vue
import { SOME_MUTATION } from './mutation-types'
this.$store.commit(SOME_MUTATION);
Mutation 必须是同步函数
Vuex要求Mutation里的方法必须是同步方法,主要原因是:
- devtools可以帮助我们捕捉mutation的快照
- 然而, mutation 中的异步函数中的回调让这不可能完成
4)、Action
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
一个简单的 action:
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
//在 action 内部可以执行异步操作
incrementAsync (context) { //context是与store 实例具有相同方法和属性的对象
setTimeout(() => {
context.commit('increment')
}, 1000)
}
}
})
分发 Action
Action 通过 store.dispatch
方法触发:
this.$store.dispatch('increment')
载荷(Payload)
// 以载荷形式分发
store.dispatch('incrementAsync', {
amount: 10
})
// 以对象形式分发
store.dispatch({
type: 'incrementAsync',
amount: 10
})
执行后回调
-
可以利用 Action 的载荷来实现——在参数中传递一个参数
-
利用promise实现
//index.js,actions中的函数返回一个promise对象 actions: { incrementAsync (context, message) { return new Promise(((resolve, reject) => { setTimeout(() => { console.log(message); context.commit('increment') resolve('成功了!'); //成功 }, 1000) })) } }
this.$store .dispatch('incrementAsync', '111') .then((msg) => { //成功之后调用 console.log(msg); }) .catch((msg) => { //失败之后调用 console.log(msg); })
this.$store.dispatch('incrementAsync', '111')
执行之后就会把返回结果替代为自身。
5)、Module
因为使用了单一状态树,只允许有一个Store对象,但是当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决这个问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块。
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 的状态
模块的局部状态
const moduleA = {
state: {
name: 'zhangsan'
},
mutations: {
updateName(state) { // 这里的 `state` 对象是模块的局部状态
state.name = 'lisi';
}
},
actions: {
asyncUpdateName(context) {
console.log(context);
context.commit('updateName')
}
},
getters: {
fullName(state, getters, rootState) {
return state.name + rootState.count;
}
}
}
const store = new Vuex.Store({
modules: {
a: moduleA
}
})
$store.state.a.name
this.$store.commit('updateName')
this.$store.dispatch('asyncUpdateName')
this.$store.getters.fullName
5、项目结构
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 # 产品模块