官方文档:https://vuex.vuejs.org/zh/
什么是 Vuex ?官方的解释:Vuex
是一个专为 Vue.js
应用程序开发的状态管理模式。
状态管理模式、集中式存储管理这些名词听起来就非常高大上,让人捉摸不透。那么状态管理到底是什么?
其实,你可以简单的将其看成把需要多个组件共享的变量全部存储在一个对象里面。然后,将这个对象放在顶层的Vue实例中,让其他组件可以使用。那么,多个组件就可以共享这个对象中的所有变量属性了,并且这些变量属性是响应式的。
单界面的状态管理,也就是指 vue 的响应式的状态原理
State
:不用多说,就是我们的状态。(你姑且可以当做就是data中的属性)View
:视图层,可以针对State的变化,显示不同的信息。Actions
:这里的Actions主要是用户的各种操作:点击、输入等等,会导致状态的改变。Vue 已经做好了单个界面的状态管理,但是如果是多个界面呢?多个视图都依赖同一个状态(一个状态改了,多个界面需要进行更新)
不同界面的 Actions 都想修改同一个状态,因此这些状态就不能只属于一个视图,需要一个大管家来统一帮助我们管理。Vuex就是为我们提供这个大管家的工具(全局单例模式)。
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中方法')
Vuex 提出使用单一状态树,英文名称是Single Source of Truth
,也可以翻译成单一数据源。即数据都保存在一个 Store 对象中
如果你的状态信息是保存到多个Store对象中的,那么之后的管理和维护等等都会变得特别困难。
所以 Vuex 使用了单一状态树来管理应用层级的全部状态。单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便的管理和维护。
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
官方原话:更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。
每个 mutation
都有一个字符串的 事件类型 (type
) 和 一个 回调函数 (handler
)。
state
定义 mutations:
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
// 变更状态
state.count++
}
}
})
状态更新:
this.$store.commit('increment')
在通过 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
})
提交 mutation 的另一种方式是直接使用包含 type 属性的对象:
this.$store.commit({
type: 'increment',
amount: 10
})
当使用对象风格的提交方式,整个对象都作为载荷传给 mutation 函数(包括 type 属性),因此 handler 保持不变:
mutations: {
// 上面提交的整个对象会传递给 payload
increment (state, payload) {
console.log(payload);
state.count += payload.amount
}
}
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')
使用常量替代 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 )
一条重要的原则就是要记住 mutation 必须是同步函数。
每一条 mutation
被记录,devtools
都需要捕捉到前一状态和后一状态的快照。如果 mutation
中是异步函数,devtools
不知道什么时候回调函数实际上被调用,固将捕捉不到后一状态的快照。
因此,如果是异步操作,需要使用 Action
Action 类似于 mutation,不同在于:
Action
提交的是 mutation
,而不是直接变更状态。Action
可以包含任意异步操作。actions
中定义异步操作,定义的函数第一个形参默认是 context
对象,这个对象是一个与 store
实例具有相同方法和属性的对象
context.commit()
:提交一个 mutation
context.state
:获取 state
context.getters
:获取 getters
。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)
}
}
}
在 Vue 组件中,通过 dispatch
来调用 action
中的方法
// 直接分发,不携带参数
this.$store.dispatch('aDecrement')
// 以载荷形式分发
this.$store.dispatch('aDecrement', {
amount: 10
})
// 以对象形式分发
this.$store.dispatch({
type: 'aDecrement',
amount: 10
})
在 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 是模块的意思,Vue 使用单一状态树,那么也意味着很多状态都会交给 Vuex 来管理。当应用变得非常复杂时,store对象就有可能变得相当臃肿。
为了解决这个问题,Vuex 允许我们将 store 分割成模块( Module ),而每个模块拥有自己的 state、mutations、actions、getters 等
mutation
的第一个形参 state 是当前模块(局部)的 stateaction
,局部状态通过 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
}
})
访问模块中的 state,通过 store.state.模块名.局部状态名
{{$store.state.a.name}}
访问模块中的 getters 、mutations、actions,直接访问,没有变化
$store.commit('increment')
$store.getters.doubleCount
$store.dispatch('incrementIfOddOnRootSum')
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 # 产品模块
定义一个对象
const obj = {
id: '123',
name: '张三',
age: 23
}
如果要获取这个对象中的属性,可以通过对象解构的方式:
// 根据变量名与对象中的属性名是否匹配来获取值
let {name, age} = obj;
这种方式可以快速取出对象中需要的属性,注意变量名要与对象属性名相同。
如果形参传入一个对象,但是只需要其中的部分属性,可以这样写:
getObjParam({name, age}){
console.log(name);
}