Vuex
Vuex
Vuex是一种状态管理器,它集中管理应用中所有组件的状态,并约定了状态更新的规则。Vuex的存在主要是为了解决多组件间的数据共享和更新问题。
Vuex适合大型单页面应用,小页面或局部状态管理时,可使用简单的store模式进行状态管理。如下:
创建store对象,并放置到data中;store对象中的state变更,都通过自身的 action 集中管理;这种集中式状态(数据)管理能够统一状态的出入口,便于跟踪和记录状态的更新过程,同时在使用时也能够让代码意图更加明显;
// store对象
var store = {
debug: true,
state: {
message: 'Hello!'
},
setMessageAction (newValue) {
if (this.debug) console.log('setMessageAction triggered with', newValue)
this.state.message = newValue
},
clearMessageAction () {
if (this.debug) console.log('clearMessageAction triggered')
this.state.message = ''
}
}
// Vue实例
var vmA = new Vue({
data: {
privateState: {},
sharedState: store.state
}
})
State
Vuex
中的数据和Vue
实例中的data
遵循相同的规则;如下,将store
对象提供给Vue
实例中的“store”选项,就可以把store
对象注入到所有子组件,且子组件能通过this.$store
访问到:
const app = new Vue({
el: '#app',
store,
components: { Counter },
template: `
<div class="app">
<counter></counter>
</div>
`
})
Getters
Getters可以返回属性或方法;Getters返回的属性相当于store实例的计算属性,其返回值会被缓存起来,只有当依赖值发生了改变才会被重新计算。
1>. Getters返回属性
// Vuex
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
],
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done);
},
doneTodosCount:state => {
return state.todos.length;
},
doneTodosCount1: (state, getters) => { // 也可以接受其他参数,如:getter作为第二个参数
return getters.doneTodos.length;
}
},
// 使用
this.$store.getters.doneTodos
this.$store.getters.doneTodosCount
2>. Getters返回函数
// Vuex -> Getters
getters: {
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
},
// 使用:getter方法不会缓存,每次都会调用执行
this.$store.getters.getTodoById(2)
Mutation
更改store
中状态的唯一方法是提交mutation
,mutation
非常类似于事件,如下:increment
是事件类型 (type),increment
是事件回调 (handler):
// Vuex
mutations: {
increment (state) {
// 变更状态
state.count++
}
},
// 使用
store.commit('increment')
1>. 提交载荷(Payload)
向 store.commit
传入额外参数,即 mutation 的 载荷(payload):
// Vuex
mutations: {
increment (state, n) {
state.count += n
}
}
// 使用
store.commit('increment', 10)
2>. 提交载荷(Payload) - 以对象方式
// Vuex
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
// 方式一:对象作为参数
store.commit('increment', {
amount: 10
})
// 方式二:直接以对象方式提交,在对象中标明mutations事件类型即可,increment回调函数在定义上不用作出任何改变
store.commit({
type: 'increment',
amount: 10
})
3>. 常量命名Mutation事件类型
使用常量命名Mutation事件类型常用于大型项目,或为了便于第三方工具分析代码之类的用途,如下:
mutations: {
[SOME_MUTATION] (state) { // 使用常量作为函数名
// mutate state
}
}
4>. Mutation必须是同步函数
mutation回调函数中的代码必须是同步的;因为mutation回调函数的存在,很大程度上是为了记录状态变更和代码调试,如果其中的代码是异步执行,则状态记录会变的不准确,且不可追踪。
例如:调用了两个包含异步回调的 mutation 来改变状态,程序中根本就无法确定哪个先执行,哪个后执行。
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')
}
}
})
Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此可以调用 context.commit
提交 mutation,或通过 context.state
和 context.getters
来获取 state 和 getters。
实践中,我们会经常用到 ES2015 的 参数解构 来简化代码,特别是需要多次调用 commit
时:
actions: {
increment ({ commit }) {
commit('increment')
}
}
1>. 分发 Action
Action 通过 store.dispatch
方法触发:
store.dispatch('incrementAsync')
Actions支持载荷和对象方式进行分发:
// Vuex
actions: {
incrementAsync ({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
// 以载荷形式分发
store.dispatch('incrementAsync', {
amount: 10
})
// 以对象形式分发
store.dispatch({
type: 'incrementAsync',
amount: 10
})
Action 常用于异步调用和分发多重 mutation 的场景,如下:
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)
)
}
}
2>. 组合 Action
store.dispatch
可以处理 action 函数返回的 Promise,并且 store.dispatch
仍旧返回 Promise:
// Vuex
actions: {
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}
// 使用
store.dispatch('actionA').then(() => {
// ...
})
Action 中也可以触发其它 Action:
actions: {
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}
也可以使用 async/await 或 组合 action:
// 假设 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 对象变得臃肿,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 的状态
1>. 局部状态
对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象:
const moduleA = {
state: () => ({
count: 0
}),
mutations: {
increment (state) {
// 这里的 `state` 对象是模块的局部状态
state.count++
}
},
getters: {
doubleCount (state) {
return state.count * 2
}
}
}
对于模块内部的 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
}
}
}
2>. 命名空间
默认情况下,模块内的 action、mutation 和 getter 注册在全局命名空间中,这使得各个模块都能够对同一类型名称的 mutation 或 action 作出响应。通过添加 namespaced: true
使模块带有命名空间,之后在外部调用时就需要标明命名空间,但同一模块中的 action、getters 相互调用时无须指明命名空间。无论是否有命名空间,模块内都可以获取到全局或和局部的state
对象,并相互调用;
调用时,模块名称作为路径名称,模块之间可持续向下嵌套:
$store.commit("moduleA/increment")
$store.commit("moduleA/moduleB/increment")
详见:命名空间
3>. 模块动态注册
使用 store.registerModule
方法可动态注册模块;动态注册模块使得第三方 Vue 插件可通过在 store 中附加新模块的方式来使用 Vuex 管理状态。
详见:模块动态注册
辅助函数
mapState、mapGetters、mapMutations、mapActions等辅助函数仅仅是将 store 中的 state、getter悔恨映射到Vue实例的局部属性;
官网对Vuex作了详细描述,建议多多阅读官方文档:https://vuex.vuejs.org/zh/