前言(可不看)
今天写一个简单的需求里面涉及到vuex,然后我想在组件中获取一个getters,就那么下手的一瞬间不知道怎么取了。很懵逼,迅速看了一下官网;还是基础知识不扎实,借着中午的休息时间总结一下vuex的基础用法,以及看官网不太好理解的modules,namespaces(朋友们你们也可以直接看vuex官网)。
基础用法
1. 安装注入
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})
// 可以获取store的state
console.log(store.state.count)
此时可以通过 store.state 来获取状态对象,以及通过 store.commit 方法触发状态变更。
为了在 Vue 组件中访问 this.$store property,你需要为 Vue 实例提供创建好的 store。Vuex 提供了一个从根组件向所有子组件,以 store 选项的方式“注入”该 store 的机制:
new Vue({
el: '#app',
store: store,
})
// 在任意组件中可以通过this.$store访问 store了
console.log(this.$store.state.count)
2. state
创建
const store = new Vuex.Store({
state: {
count: 0
}
})
在 Vue 组件中展示状态
- 在 Vue 组件中展示状态:由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态:
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return this.$store.state.count
}
}
}
- mapState辅助函数 :当一个组件需要获取很多个状态时,全部放在computed有些厚重和冗余,为了解决这个问题可以用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
}
})
}
当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。
computed: mapState([
// 映射 this.count 为 store.state.count
'count'
])
mapState 函数返回的是一个对象。我们可以通过展开运算符将它与局部计算属性混合使用;
computed: {
localComputed () { /* ... */ },
// 使用对象展开运算符将此对象混入到外部对象中
...mapState({
// ...
})
}
3. getters
有时候我们需要从 store 中的 state 中派生出一些状态,比如列表过滤等,要是有多个地方需要用到这个派生的状态,每次都过滤不是很优雅;
创建
Vuex 允许我们在 store 中定义“getter”(** store 的计算属性**)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
// Getter 接受 state 作为其第一个参数
doneTodos: state => {
return state.todos.filter(todo => todo.done)
},
// Getter 接受其他 getter 作为第二个参数
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
})
在组件中使用 getter
- getter 在通过属性访问时是作为 Vue 的响应式系统的一部分缓存其中的。
computed: {
doneTodosCount () {
return this.$store.getters.doneTodosCount
}
}
- getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果。
// 可以通过让 getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时非常有用。
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'
})
}
}
4. mutations
- 更改Vuex 的 store 中的状态的唯一方法是提交 mutation。
- 使用常量替代 Mutation 事件类型
- Mutation 必须是同步函数
创建
Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型(名称) (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
// 变更状态
state.count++
}
}
})
也可以传入额外的参数,即 mutation 的 载荷(payload)作为第二个参数:
在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读:
mutations: {
increment (state, n) {
state.count += n
},
increment2 (state, payload) {
state.count 2+= payload.amount
}
}
store.commit('increment', 10)
store.commit('increment2', {
amount: 10
})
提交 mutation 的另一种方式是直接使用包含 type 属性的对象:
当使用对象风格的提交方式,整个对象都作为载荷传给 mutation 函数,因此 handler 保持不变:
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
store.commit({
type: 'increment',
amount: 10
})
在组件中提交 Mutation
- this.$store.commit(‘xxx’)
- mapMutations 辅助函数
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')`
})
}
}
5. actions
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 对象,可以console.log(context);因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters… 还可以增加payload(载荷)来传递额外的参数。
actions: {
// ...
actionB ({ state, getters, dispatch, commit },payload) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}
context不是store实例本身,因为在modules中它是module 的实例,module 相当于一个小store.
在组件中分发 Action
this.$store.dispatch('xxx')
分发 action- 使用 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
Action 通常是异步的,那么如何知道 action 什么时候结束呢?我们需要明白 store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise:
actions: {
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
},
// 应用在另外一个 action
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}
// 假设 getData() 和 getOtherData() 返回的是 Promise
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}
modules用法
背景
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex
允许我们将 store 分割成模块(module)。module:可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。
const moduleA = {
state: { ... }, // 局部state
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... }, // 局部state
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
state:{ ... }, // 全局state
modules: {
a: moduleA,
b: moduleB
}
})
模块内部数据作用域
关于模块内部数据的作用域与namespace:true
属性相关,因此分为两个点:
-
无namespace
- state 是模块内部的(局部state):moduleA和moduleB都是,访问moduleA中的
state :store.state.a.xxx
- getters 参数
(state,getters, rootState)
state是局部state,getters是全局getters,rootState是全局state - mutations 参数
(state,payload)
state是局部state - actions 参数
({state, commit, roosState...})
state是局部state, rootState是全局state。 commit提交的全局下的mutations(使用限制看下面)
- state 是模块内部的(局部state):moduleA和moduleB都是,访问moduleA中的
-
有namespace
什么是命名空间,就是在module中添加一个属性,namespaced: true
, 就完成了,而名字就是在全局中导入的module的名字,上边代码中moduleA对应的a为其名字。
module中state
不受命名空间影响,本身就是局部state的
getters
和mutations
,actions
在命名后为局部,不能直接在全局调用- state 依然是局部
state
- getters 参数
(state,getters, rootState, rootGetters)
state是局部state ,getters是局部getters,rootState是全局state,rootGetters是全局getters - mutations 参数
(state,payload)
state是局部state - actions 参数
({state, commit, roosState, rootGetters...})
state是局部state, rootState是全局state,rootGetters是全局getters;commit提交的局部的mutations(使用限制看下面)
- state 依然是局部
模块内部数据访问限制
关于模块内部数据的访问也与namespace:true
属性相关,因此也分为两个点:
-
无namespace
- state :在局部
state :this.$store.state.a.xxx
- getters : 在全局
...mapGetters(["localJobTitle"])
不需要module名 - mutations:在全局 commit也不需要module名;
this.$store.commit('xxx')
- actions:在全局 dispatch 不需要module名;
this.$store.dispatch('xxx')
- state :在局部
-
有namespace
- state:在局部
state:this.$store.state.a.xxx
- getters:在局部
this.$store.getters['a/xxxx']
- mutations:在局部
this.$store.commit['a/xxxx']
- actions:在局部
this.$store.dispatch['a/xxxx']
- state:在局部
因为命名空间,
getters
和mutations
,actions
为局部,不能直接在全局调用,所以如何使用全局的commit
和dispatch
呢?
通过传入{root:true}
作为第三个参数打开路径调用全局的commit
和dispatch
actions: {
// 在这个模块中, dispatch 和 commit 也被局部化了
// 可以接受 `root` 属性以访问根 dispatch 或 commit
someAction ({ dispatch, commit, getters, rootGetters }) {
dispatch('someOtherAction') // -> 'foo/someOtherAction'
dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'
commit('someMutation') // -> 'foo/someMutation'
commit('someMutation', null, { root: true }) // -> 'someMutation'
},
如果你想在
module
中注册一个全局的actions
,可以通过如下方式:
在actions中添加一个属性(action函数名字),通过对象方式,添加root:true
的属性以及在handle中添加action方法{root:true,handle(){...}}
actions: {
someAction: {
root: true,
handler (namespacedContext, payload) { ... } // -> 'someAction'
}
}
如果你觉得需要些指定命名的路径比较麻烦,你可以调用createNamespacedHelpers这个创建某个命名空间的辅助函数
import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('moduleA')
...mapState({
a: state => state.a,
b: state => state.b
})
...mapActions([‘foo’]) =>使用为 this.foo()
...mapMutations([‘foo’]) =>使用为 this.foo()