Vuex
开始
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import Vue from "vue"; import Vuex from "vuex"
Vue.use(Vuex)
const store = new Vuex.Store({ state: { count: 0 }, mutation: { increment: { state.count++ } } })
|
- 通过操作
mutation
来改变state
:
- 通过
store.state
获取状态,store.commit('increment')
触发状态变更
- 在Vue实例中,也要提供创建好的
store
, 方便组件中访问 this.$store property
1 2 3 4 5 6 7 8 9 10
| new Vue({ el: '#app', store: store, methods: { increment() { this.$store.commit('increment') console.log(this.$store.state.count) } } })
|
State
mapState
1 2 3 4 5 6
| template: `<div>{{ count }}</div>`, computed: { count () { return this.$store.state.count } }
|
- 为了反复申明
computed
,使用展开符和mapState统一添加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { mapState } from 'vuex'
export default { computed: { localComputed() {...} ...mapState({ count: state => state.count, countAlias: "count", countPlusLocalCount (state) { return this.localCount + state.count } }) } }
|
- 当映射的
computed
的名称与 state
的子节点名称相同时,我们也可以传一个字符串数组。
1 2 3 4 5
| computed: mapState([ 'count', 'increment' ])
|
Getter
- 类似于store 的计算属性
- 第一个参数接受
state
第二个接受其他getters
1 2 3 4 5 6 7 8
| getters: { doneTodos: state => { return state.todos.filter(todo => todo.done) }, doneTodosCount: (state, getters) => { return getters.doneTodos.length } }
|
- 通过
this.$store.getters.doneTodosCount
在组件中使用它
通过属性访问时每次都会去进行调用,而不会缓存结果
也不能直接当作 方法 来使用,因为只是一个属性
通过方法访问
- 让getter返回一个函数,来作为方法传参:
store.getters.getTodoById(2)
1 2 3 4 5
| getters: { getTodoById: state => id => { return state.todos.find(todo => todo.id === id) } }
|
mapGetters
- 辅助将 store 中的 getter 映射到局部计算属性
1 2 3 4 5 6 7 8 9 10 11 12 13
| import {mapGetters} from "vuex"
export default { computed: { ...mapGetters({ 'doneTodosCount', 'anotherGetter', toThisName: 'changeName' }) } }
|
Mutations
- 类似于事件注册,真正的事件更改应该用
store.commit("event")
commit payload
1 2 3 4 5 6 7 8 9
| mutations: { increment(state, payload) { state.count += payload.amount } }
store.commit("increment", { amount: 10 })
|
commit对象 风格
1 2 3 4
| store.commit({ type: 'increment', amount: 10 })
|
添加新属性时
- 使用
Vue.set(obj, 'newProp', 123)
, 或者 新的替换老的state.obj = { ...state.obj, newProp: 123 }
Mutations 必须是同步函数
- 实际上,任何在回调函数中进行的状态的改变都是不可追踪的。
组件中提交Mutation: mapMutations
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import {mapMutations} from "vuex"
export default { methods: { ...mapMutations([ "increment", "incrementBy" ]), ...mapMutation({ add: "increment" }) } }
|
Actions
- 类似于Mutations,不同在于:
- 处理异步操作
- 提交的是Mutation,而不是直接更改状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment(state) { state.count++ } }, actions: { increment(context) { context.commit("increment") } } })
|
- Action函数 接受一个
context
, 与 store
实例具有相同方法和属性
context.state
和 context.getters
获得state
和 getters
context.commit()
提交一个mutation
解构语法
1 2 3 4 5
| actions: { increment( {commit} ) { commit("increment") } }
|
分发Actions: store.dispatch
- 为了实现异步操作, 通过
store.dispatch
触发Action
1 2 3 4 5 6 7
| actions: { incrementAsync ({commit}) { setTimeout(() => { commit("increment") }, 1000) } }
|
1 2 3 4 5 6 7 8 9 10
| store.dispatch("incrementAsync", { amount: 10 })
store.dispatch({ type: "incrementAsync", amount: 10 })
|
购物车事例
- 通过提交 mutation 来记录action产生的状态变更。
1 2 3 4 5 6 7 8 9 10 11 12 13
| actions: { checkout({commit, state}, products) { const saveCartItems = [...state.cart.added] commit(types.CHECKOUT_REQUEST) shop.buyProducts( products, () => commit(types.CHECKOUT_SUCCESS), () => commit(types.CHECKOUT_FAILURE, saveCartItems) ) } }
|
在组件中分发 Action: mapActions
this.$store.dispatch('xxx')
或者 mapActions
辅助函数将组件的 methods 映射为dispatch调用
组合Actions
- dispatch 可以处理被触发的Action 所返回的Promise,
store.dispatch
返回 Promise
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| actions; { actionA ( {commit} ) { return new Promise((res, rej) => { setTimeout(() => { commit("someMutation") res() }, 1000) }) }, actionB ({dispatch, commit}) { return dispatch("actionA").then(() => { commit("someOtherMutation") }) } }
|
async / await
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| actions: { async actionA({commit}) { commit("gotData", await gotData()) }, async actionB({dispatch, commit}) { await dispatch("actionA") commit("gotOtherData", await gotOtherData())
await dispatch('actionA') const response = await getOtherData() commit('gotOtherData', response) } }
|
Modules
- 模块有自己的
state
mutations
actions
getters
, 或者嵌套模块 modules
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| const moduleA = { state: () => {...}, mutations: {}, actions: {}, getters: {} };
const moduleB = { state: () => {...}, mutations: {}, actions: {}, getters: {} };
const store = new Vuex.Store({ modules: { a: moduleA, b: moduleB, } })
store.state.a
|
模块内的局部状态
- 对于模块内的
getters
和 mutations
:模块内的局部状态 state
是第一个参数
- 对于模块内
actions
: 获取局部状态通过 context.state
;根结点 context.rootState
1 2 3 4 5 6
| actions: { incrementIfOddOnRootSum ({ state, commit, rootState }) { if ((state.count + rootState.count) % 2 === 1) { commit('increment') } }
|
- 对于模块内的
getters
: 根节点状态会作为第三个参数暴露出来1 2 3 4 5
| getters: { sumWithRootCount (state, getters, rootState) { return state.count + rootState.count } }
|
命名空间 namespaced: true
- 在
modules
中,可以命名各个module,并添加 namespaced: true
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const store = new Vuex.Store({ modules: { account: { namespaced: true, state: () => ({...}), getters: { isAdmin() {...} }, actions: { login() {...} }, mutations: { login() {...} } } } })
|
带命名空间的模块 访问 全局内容
- 使用全局内容
- getters:
someGetter (state, getters, rootState, rootGetters) {...}
- actions:
someAction ({ dispatch, commit, getters, rootGetters }) {...}
or context.rootGetters
- 分发全局内容: 将
{ root: true }
作为第三参数
dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'
而不是 'account/someOtherAction'
commit('someMutation', null, { root: true }) // -> 'someMutation'
带命名空间的模块 注册 全局action
- 添加
root: true
, 并将其定义放在 handle
中
1 2 3 4 5 6 7 8 9 10 11
| actions: { callAction: { root: true, handler (namespacedContext, payload) { let {state, commit} = namespacedContext; commit('setText'); alert(state.text); } } }
|
带命名空间的 绑定函数
- 可以将模块的空间名称字符串作为第一参数传递给
mapState
, mapGetters
, mapActions
和 mapMutations
1 2 3 4 5 6 7 8 9 10 11
| computed: { ...mapState("some/nested/module", { a: state => state.a }) }, methods: { ...mapActions("some/nexted/module", [ "foo", "bar" ]) }
|
- 或者利用
createNamespacedHelpers
创建基于某个命名空间辅助函数
1 2 3 4 5 6 7 8 9 10 11
| import { createNamespacedHelpers } from 'vuex' const { mapState } = createNamespacedHelpers('some/nested/module')
export default { computed: { ...mapState({ a: state => state.a, b: state => state.b }) }
|
模块动态注册 store.registerModule
1 2 3 4 5 6 7 8 9 10
| const store = new Vuex.Store({ ... })
store.registerModule("myModule", {...})
store.registerModule(['nested', 'myModule'])
store.state.myModule store.state.nested.myModule
|
保留state preserveState: true
- 假设 store 的 state 已经包含了这个 module 的 state , 并且不希望将其覆写
- 注册一个新module时,保留过去的state:
store.registerModule('a', module, { preserveState: true })
模块重用
- 场景:
- 多个store公用同一模块
- 在一个store中,模块被多次注册
- 多次引用 state 就会通过引用被共享,导致状态对象被修改时 store 或模块间数据互相污染的问题。
- 类似于 data 的问题,所以用 函数申明 代替 对象申明
1 2 3 4 5
| const reusableModule = { state: () => ({ foo: "bar" }) }
|