参考来源:
Vex 是什么?
vuex 是一个专为 vue 应用程序开发的 状态管理模式。保证状态以一种可预测的方式发生变化。
Vuex 作用一览:
简单的例子🌰:
// Vue.use(Vuex)
const Store = new Vuex.Store({
state: {
count: 0,
},
mutations: {
increment (state) {
state.count ++
}
}
})
// 使用
store.commit('increment');
console.log(store.state.count); // -> 1
约定通过提交 mutation 的方式,而非直接改变 store.state.count
,更加明确追踪到状态的变化。
一个 Vuex 的 Store 分为四个部分:
- State
- Getter
- Mutation
- Action
State(单一状态树)
在vue中使用 State 状态的最简单的方法就是在计算属性(computed)中返回某个状态值:
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count() {
return this.$store.state.count
}
}
}
上述过程中,每当 store 中的 count 状态值发生变化,computed 属性都能监听得到,并且触发相关联的 DOM的更新。
mapState
辅助函数
当一个组件需要获取多个状态值,将这些状态都声明在计算属性中会显得臃肿不堪,Vuex 提供了一个 mapState
函数简化这一操作:
import { mapState } from 'vuex';
export default {
//...
computed: {
otherComputed() {},
// ...
...mapState({
count: state => state.count,
// 传字符串参数 'count' 等同于 `state => state.count`
countAlias: 'count',
// 为了能够使用 `this` 获取局部状态,必须使用常规函数
countPlusLocalState(state) {
return state.count + this.localCount
}
})
}
}
Getter
有时候我们需要从 Store 中的 State 派生出一些状态,例如对列表进行过滤并记数:
computed: {
doneTodosCount() {
return this.$store.store.todos.filter(todo => todo.done).length
}
}
如果有多个组件需要用这个属性,那么就需要在每个组件中都写这么长的表达式,难免有些尴尬😅。
这时候 Getter 就派上了用处(可以认为是 Store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,依赖变了,返回值也随之改变。
const store = new Vuex.store({
state: {
todos: [
{ id: 1, text: '...', done: true},
{ id: 1, text: '...', done: false},
],
},
getters: {
doneTodos: state => state.todo.filter(todo => todo.done)
}
})
在组件中通过使用 store.getters
对象,可以以属性的形式方为这些值:
store.getters.doneTodos
// -> [{ id: 1, text: '...', done: true }]
Getter也可以接受其他 getter 作为第二个参数:
getters: {
// ...
doneTodosCount: (state, getters) => getters.doneTodos.length,
}
// 使用
store.getters.doneTodosCount // -> 1
通过方法访问
可以对 getter 的返回值做些手脚:
getters: {
// ...
getTodosById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
// 使用
store.getters.getTodoById(2)
// -> { id: 2, text: '...', done: false }
使 getter 返回一个函数时,可以适应许多常用场景。getter 在使用 方法访问时,每次都会进行调用而不会缓存结果。
mapGetters
辅助函数
类似于 mapState
,vuex 也提供了 mapGetters
方便使用:
import { mapGetters } from 'vuex'
export default {
//...
computed: {
// 使用对象展开运算符将 getter 混入 computed对象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
//...
])
}
}
Mutation
更改 Vuex 的 store 的状态的唯一方法就是提交 mutation。每个 mutation 都有字符串的 事件类型(type) 和一个 回调函数(handler):
const store = new Vuex.store({
state: {
count: 1,
},
mutation: {
icrement (state) {
state.count ++
},
decrement (state, { delta }) {
state.count -= delta
}
}
});
// 使用
store.commit('icrement');
store.commit('decrement', { delta: 10 });
// 对象风格的提交方式
store.commit({
type: 'decrement',
delta: 10,
})
Mutation 需遵守 Vue 的响应规则:
提前初始化 store 中所有的属性。
-
当需要在对象上添加新属性时
使用
Vue.set(obj, 'newProp', 123)
或者-
以新对象替换老对象,例如:
state.obj = { ...state.obj, newProp: 123 }
使用常量替代 Mutation 事件类型
使用常量替代 mutation 事件类型在各种 Flux 实现中是很常见的模式。这样可以使 linter 之类的工具发挥作用,同时把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation 一目了然:
// mutation-types.js
export const SOME_MUTATION = 'SOME_'
使用类型常量:
// store.js
import Vuex from 'vuex';
import { SOME_MUTATION } from './mutation-types'
const store = new Vuex.store({
state: { /* ... */ },
mutations: {
// 使用 ES6 风格的常量作为函数名。
[SOME_MUTATION] (state) {
// mutate state
}
}
})
Mutation 必须是同步函数 ⚠️
任何回调函数中进行的状态的改变都是不可追踪的!在 mutation 中混合异步调用会导致你的程序很难调试。
在组件中提交 Mutation
在组件中使用
this.$store.commit('xxx')
提交 mutation;-
使用
mapMutation
辅助函数将 mutation 方法混入组件的 methods 中:import { mapMutatioins } from 'Vuex'; export default { // other props methods: { ...mapMutations([ 'increment',// 将 `this.increment()` 映射为 `this.$store.commit('increment')` // `mapMutations` 也支持载荷: 'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)` ]), ...mapMutations({ add: 'increment', // 将 `this.add()` 映射为 `this.$store.commit('increment')` }) } }
Action
Action 类似于 Mutation,不同于:
- Action 提交的是 mutation,而不是直接变更状态;
- Action 可以包含任意异步操作;
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
},
// 参数结构:
decrement ({ commit }) {
commit('decrement')
}
}
});
Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此可以调用 context.commit
方法提交一个 Mutation,或者使用 context.state
和 context.getters
获取 state 和 getters。
⚠️context 对象并不是 store 实例本身
分发 Action
store.dispatch('increment');
与 Mutation 不一样的是,在 Action 内部是可以执行 异步 操作:
actions: {
incrementAsync ({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
在组件中分发 Action
import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
// `mapActions` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
})
}
}
组合 Action
store.dispatch
可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch
仍旧返回 Promise:
actions: {
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
},
// ...
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}
// 使用:
store.dispatch('actionB').then(() => {
// ...
})
以及比较复杂的使用:
// 假设 getData() 和 getOtherData() 返回的是 Promise
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}
规则建议:
- 应用层级的状态应该集中到单个 store 对象中。
- 提交 mutation 是更改状态的唯一方法,这个过程是同步的。
- 异步逻辑都应该封装到 action 中。
相关面试题
📒 什么时候用 Vuex
- 多个组件依赖于同一状态。
- 来自不同组件行为需要更改同一状态。
📒 Vuex 中状态是对象时,使用时要注意什么?
对象时引用类型,复制后改变属性还是会影响原始数据,所以在赋值前,先试用DeepClone
📒 怎么在组件中批量使用Vuex的state状态?
使用mapState辅助函数, 利用对象展开运算符将state混入computed对象中
import {mapState} from 'vuex'
export default{
computed:{
...mapState(['price','number'])
}
}
📒 Vuex中要从state派生一些状态出来,且多个组件使用它,该怎么做?
使用 getter 属性:
const store = new Vuex.store({
state: {
price: 10,
number: 10,
discount: 0.7,
},
getters: {
total: state => state.price * state.number,
discountTotal: (state, getters) => state.discount * getters.total,
},
})
面试问题待收录