Vuex是什么?
状态管理模式。
集中式存储管理所有组件状态,及其变化。
可用Vue官方devtools调试。
什么是"状态管理模式"
以此为例:
new Vue({
// state
data () {
return {
count: 0
}
},
// view
template: `
<div>{{ count }}</div>
`,
// actions
methods: {
increment () {
this.count++
}
}
})
包含三部分:
- state 驱动应用的数据源
- view 将state映射到视图
- actions 响应状态变化

但是当多个组件共享状态时,单向数据流就会被破坏。
兄弟组件间状态传递无力,父子组件传递麻烦,所以直接抽取出来共享状态,做一个全局单例模式的管理。
更结构化更稳健更容易维护。
什么时候用Vuex?
中大型单页应用。
开始
核心是store。
和单纯的全局对象有两点不同:
- Vuex是响应式的。
- 不能直接改变store中的状态。唯一途径是显式的commit和mutation。这样才能跟踪变化。
最简单的store
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})
store.commit('increment')
console.log(store.state.count) // -> 1
核心概念
Vuex图示

state
单一状态数
用一个对象包含了全部的状态。
state必须是纯粹的对象(含有零个或多个的 key/value 对)。
在组件中获取Vuex状态
最简单的就是在计算属性中返回:
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return store.state.count
}
}
}
在根组件注册store
const app = new Vue({
el: '#app',
// 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件
store,
components: { Counter },
template: `
<div class="app">
<counter></counter>
</div>
`
})
每个子组件可以通过$store访问:
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return this.$store.state.count
}
}
}
mapState辅助函数
一个组件过去多个状态的时候:
// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex'
export default {
// ...
computed: mapState({
// 箭头函数可使代码更简练
count: state => state.count,
// 传字符串参数 'count' 等同于 `state => state.count`
countAlias: 'count',
// 为了能够使用 `this` 获取局部状态,必须使用常规函数
countPlusLocalState (state) {
return state.count + this.localCount
}
})
}
当同名的时候:
computed: mapState([
// 映射 this.count 为 store.state.count
'count'
])
对象展开运算符
mapState函数返回一个对象。
将它与局部计算属性混合使用:
computed: {
localComputed () { /* ... */ },
// 使用对象展开运算符将此对象混入到外部对象中
...mapState({
// ...
})
}
Getter
在store中定义getter,相当于store的计算属性。
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会暴露为store.getters对象
store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]
getter也接受其他getter作为第二个参数
getters: {
// ...
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
通过方法访问
通过getter返回一个函数实现给getter传参。
getters: {
// ...
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
mapGetters辅助函数
将store的getter映射到局部计算属性:
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}
如果想另外取名字:
mapGetters({
// 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
doneCount: 'doneTodosCount'
})
Mutation
更改state唯一方法是提交mutation。
每个mutation都有一个字符串的事件类型(type)和一个回调函数(handler)
回调函数接受state作为第一个参数。
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
// 变更状态
state.count++
}
}
})
不能直接调用mutation的handler。
更像是:"当触发一个类型为increment的mutation时,调用此函数"。
要唤醒handler需要以相应的type调用store.commit方法:
store.commit('increment')
提交载荷(Payload)
向commit传入额外的参数,即mutation的载荷:
// ...
mutations: {
increment (state, n) {
state.count += n
}
}
store.commit('increment', 10)
载荷大部分应该是一个对象。
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
store.commit('increment', {
amount: 10
})
对象风格的提交方式
直接使用包含type属性的对象:
store.commit({
type: 'increment',
amount: 10
})
使用对象风格提交,则整个对象都作为载荷
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
Mutation需遵守Vue的响应规则
- 提前在store中初始化所需的属性
- 要给对象添加新属性时应该
- Vue.set(obj,'newProp',123)
- 新对象替换老对象
state.obj = { ...state.obj, newProp: 123 }
使用常量替代Mutation事件类型
// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
mutations: {
// 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
[SOME_MUTATION] (state) {
// mutate state
}
}
Mutation必须是同步函数
devtools无法追踪前后状态
在组件中提交Mutation
可以使用this.$store.commit('xxx')
提交
也可以使用mapMutation辅助函数将组件的methods映射为store.commit
import { mapMutations } from 'vuex'
export default {
// ...
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
类似于Mutation但:
- Action提交的是mutation,而不是直接更改状态
- 可以包含异步操作
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
接受一个与store实例有相同方法属性的context对象。但它不是store本身。
利用参数解构:
actions: {
increment ({ commit }) {
commit('increment')
}
}
分发Action
Action通过store.dispatch触发
store.dispatch('increment')
可以在action内部执行异步操作:
actions: {
incrementAsync ({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
支持载荷方式和对象方式分发:
// 以载荷形式分发
store.dispatch('incrementAsync', {
amount: 10
})
// 以对象形式分发
store.dispatch({
type: 'incrementAsync',
amount: 10
})
购物车实例:
actions: {
checkout ({ commit, state }, products) {
// 把当前购物车的物品备份起来
const savedCartItems = [...state.cart.added]
// 发出结账请求,然后乐观地清空购物车
commit(types.CHECKOUT_REQUEST)
// 购物 API 接受一个成功回调和一个失败回调
shop.buyProducts(
products,
// 成功操作
() => commit(types.CHECKOUT_SUCCESS),
// 失败操作
() => commit(types.CHECKOUT_FAILURE, savedCartItems)
)
}
}
在组件中分发Action
使用this.$store.dispatch('xxx')
或者使用mapActions辅助函数将methods映射为store.dispatch调用:
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
可处理Promise
actions: {
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}
store.dispatch('actionA').then(() => {
// ...
})
如果利用async/await:
// 假设 getData() 和 getOtherData() 返回的是 Promise
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}
Module
将store分割成模块module。
每个模块拥有自己的state,mutation,getter,action甚至嵌套子模块。
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 的状态
模块的的局部状态
模块内部的getter和mutation的第一个参数的局部状态对象。
模块内部的action,局部状态是context.state而根节点状态是context.rootState
const moduleA = {
// ...
actions: {
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
}
模块内部的getter根节点状态是第三个参数:
const moduleA = {
// ...
getters: {
sumWithRootCount (state, getters, rootState) {
return state.count + rootState.count
}
}
}
命名空间
默认注册在全局命名空间
可以添加namespaced:true让其成为带命名空间的模块。
const store = new Vuex.Store({
modules: {
account: {
namespaced: true,
// 模块内容(module assets)
state: { ... }, // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响
getters: {
isAdmin () { ... } // -> getters['account/isAdmin']
},
actions: {
login () { ... } // -> dispatch('account/login')
},
mutations: {
login () { ... } // -> commit('account/login')
},
// 嵌套模块
modules: {
// 继承父模块的命名空间
myPage: {
state: { ... },
getters: {
profile () { ... } // -> getters['account/profile']
}
},
// 进一步嵌套命名空间
posts: {
namespaced: true,
state: { ... },
getters: {
popular () { ... } // -> getters['account/posts/popular']
}
}
}
}
}
})
在带命名空间的模块内部访问全局内容
modules: {
foo: {
namespaced: true,
getters: {
// 在这个模块的 getter 中,`getters` 被局部化了
// 你可以使用 getter 的第四个参数来调用 `rootGetters`
someGetter (state, getters, rootState, rootGetters) {
getters.someOtherGetter // -> 'foo/someOtherGetter'
rootGetters.someOtherGetter // -> 'someOtherGetter'
},
someOtherGetter: state => { ... }
},
actions: {
// 在这个模块中, dispatch 和 commit 也被局部化了
// 他们可以接受 `root` 属性以访问根 dispatch 或 commit
someAction ({ dispatch, commit, getters, rootGetters }) {
getters.someGetter // -> 'foo/someGetter'
rootGetters.someGetter // -> 'someGetter'
dispatch('someOtherAction') // -> 'foo/someOtherAction'
dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'
commit('someMutation') // -> 'foo/someMutation'
commit('someMutation', null, { root: true }) // -> 'someMutation'
},
someOtherAction (ctx, payload) { ... }
}
}
}
在带命名空间的模块注册全局action
{
actions: {
someOtherAction ({dispatch}) {
dispatch('someAction')
}
},
modules: {
foo: {
namespaced: true,
actions: {
someAction: {
root: true,
handler (namespacedContext, payload) { ... } // -> 'someAction'
}
}
}
}
}
带命名空间的绑定函数
computed: {
...mapState('some/nested/module', {
a: state => state.a,
b: state => state.b
})
},
methods: {
...mapActions('some/nested/module', [
'foo', // -> this.foo()
'bar' // -> this.bar()
])
}
第二种 使用createNamespacedHelpers
import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')
export default {
computed: {
// 在 `some/nested/module` 中查找
...mapState({
a: state => state.a,
b: state => state.b
})
},
methods: {
// 在 `some/nested/module` 中查找
...mapActions([
'foo',
'bar'
])
}
}
模块动态注册
// 注册模块 `myModule`
store.registerModule('myModule', {
// ...
})
// 注册嵌套模块 `nested/myModule`
store.registerModule(['nested', 'myModule'], {
// ...
})
严格模式
创建store传入strict:true
const store = new Vuex.Store({
// ...
strict: true
})
状态变更如果不是mutation引起的就会报错。
开发环境和发布环境
不要在发布环境启用严格模式。
表单处理
<input v-model="obj.message">
如果这个obj是计算属性返回的Vuex store的对象。
这个时候输入input在严格模式下,会报错,因为不是mutation改的。
解决办法:绑定value,监听input或者change事件,提交mutation。
<input :value="message" @input="updateMessage">
// ...
computed: {
...mapState({
message: state => state.obj.message
})
},
methods: {
updateMessage (e) {
this.$store.commit('updateMessage', e.target.value)
}
}
// ...
mutations: {
updateMessage (state, message) {
state.obj.message = message
}
}
双向绑定的计算属性
使用带有setter的双向绑定计算属性
<input v-model="message">
// ...
computed: {
message: {
get () {
return this.$store.state.obj.message
},
set (value) {
this.$store.commit('updateMessage', value)
}
}
}