Vuex状态管理模式
当两个或两个以上的组件需要共用同一个数据源时,使用Vuex对数据的管理会变得更见简单轻松(相比localStorage、路由传参)
响应式
Vuex的状态存储是响应式的。当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件(视图)也会相应地得到高效更新。
核心概念
- state
- getters
- mutations
- actions
state
存放共享的数据,需要提前声明
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0
}
})
// main.js,注入store,每个组件都可以通过this.$store访问store
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store/store'
new Vue({
el: '#app',
router,
store,
render: h => h(App)
})
如何在组件中获取state中的数据?
- 直接调用
let count = this.$store.state.count
this.count = count
<span>{{count}}</span>
- 计算属性
// 由于Vuex的状态存储是响应式的,从store实例中读取状态最简单的方法就是在计算属性中返回某个状态
// 每当 store.state.count 变化的时候, 都会重新求取计算属性,并且触发更新相关联的 DOM
computed: {
count () {
return this.$store.state.count
}
}
<span>{{count}}</span>
- mapState辅助函数
// 当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。
// 为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性,让你少按几次键:
// 不用写那么多次this.$store.state.xxx
import { mapState } from 'vuex'
export default {
// mapState函数传入一个对象
computed: mapState({
// 箭头函数可使代码更简练
count: state => state.count,
// 传字符串参数 'count' 等同于 `state => state.count`。取别名~
countAlias: 'count',
// 为了能够使用 `this` 获取局部状态,必须使用常规函数
countPlusLocalState (state) {
return state.count + this.localCount
}
})
}
// mapState函数传一个字符串数组
// 数组中的变量名与state中设置时保持一致
computed: mapState([
// 映射 this.count 为 this.$store.state.count
'count'
])
- 与局部计算属性混合使用
computed: {
localComputed ()
// 使用对象展开运算符将此对象混入到外部对象中,mapState可传入对象/数组的形式
...mapState({
// ...
})
}
Getter
有时候我们需要从 store 中的 state 中派生出一些状态,例如对列表进行过滤/计数/查询
computed: {
doneTodosCount () {
return this.$store.state.todos.filter(todo => todo.done).length
}
}
如果有多个组件需要用到此属性,我们要么复制这个函数,或者抽取到一个共享函数然后在多处导入它——无论哪种方式都不是很理想。(同一个功能多个组件中都要写,不利于维护)
Vuex 允许我们在store中定义“getter”(可以认为是 store 的计算属性),就像计算属性一样,getter的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
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)
},
// 也可以接受其他 getter 作为第二个参数
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
},
// 传递参数
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
})
调用方式
// 组件调用
this.$store.getters.doneTodos()
this.$store.getters.getTodoById(2)
// mapGetters辅助函数
// mapGetters可传入数组或者对象(对象的形式可以设置别名)
import { mapGetters } from 'vuex'
export default {
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
// 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
...mapGetters({
doneCount: 'doneTodosCount'
})
}
}
//
// mapGetters传入数组
...mapGetters([
'doneTodosCount', // 通过this.doneTodosCount()调用
'anotherGetter', // 通过this.anotherGetter()调用
])
Mutation
更改Vuex的store中的状态的唯一方法是提交mutation。可以把mutation理解成函数,且必须是同步函数,因为devtools需要捕捉到前一状态和后一状态的快照,异步函数中的回调让这不可能完成
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
// 修改状态
increment (state) {
state.count++
}
}
})
// 组件调用
this.$store.commit('increment')
载荷(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
})
// 对象风格的提交方式,也写成下面这种形式
this.$store.commit({
type: 'increment',
amount: 10
})
mapMutations 辅助函数
方便调用,mapMutations函数可以对象或者数组形式传入参数
import { mapMutations } from 'vuex'
export default {
methods: {
// 传入数组
...mapMutations([
// 将 `this.increment()` 映射为 `this.$store.commit('increment')`
'increment',
// `mapMutations` 也支持载荷。将 `this.incrementBy(amount)` 映射 `this.$store.commit('incrementBy', amount)`
'incrementBy'
]),
// 传入对象(可设置别名)
...mapMutations({
// 将 `this.add()` 映射为 `this.$store.commit('increment')`
add: 'increment'
})
}
}
// 组件中调用
this.increment()
this.incrementBy(amount)
this.add()
使用常量替代 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
}
}
})
// home.js
import { SOME_MUTATION } from './../../store/mutation-types'
methods: {
handle () {
this.$sotre.commit({
type: SOME_MUTATION,
amount: 10
})
}
}
Action
- Action 提交的是 mutation,而不是直接变更状态
- Action 可以包含任意异步操作
mutation是不能进行异步操作的,比如根据一个接口的返回值来修改state,就需要用到action(action也是调用mutation来修改state,只是在action中可以进行异步操作,例如接口请求)
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment') // this.$store.dispatch('increment ')
}
}
})
// ES2015 的 参数解构 来简化代码
actions: {
increment ({ commit }) {
commit('increment')
}
}
// 异步操作
actions: {
incrementAsync ({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
调用方式
// 以载荷形式分发
this.$store.dispatch('incrementAsync', {
amount: 10 // amount传入mutation
})
// 以对象形式分发
this.$store.dispatch({
type: 'incrementAsync',
amount: 10
})
// mapActions辅助函数
import { mapActions } from 'vuex'
export default {
methods: {
...mapActions([
// 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
'increment',
// `mapActions`也支持载荷:
// 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
'incrementBy'
]),
...mapActions({
// 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
add: 'increment'
})
}
}
组合 Action
处理异步action
actions: {
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
},
// 在一个action中调用另一个action
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
},
// 使用await / async
async actionC ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}
// 组件中调用
this.$store.dispatch('actionA').then(() => {
// ...
})
this.$store.dispatch('actionB')
this.$store.dispatch('actionC')