简介
在大型的项目中,状态往往不只是父子之间传递,而是分布于各个组件中,我们通过属性传值的方式就会显得十分臃肿,并且在Vue
中,兄弟组件之间传值并没有什么好的办法,往往通过事件总线订阅发布的方式,这种方式可行,但是我们把一些共享的状态抽离出来,统一的放在一个仓库中,哪个组件需要使用,就去仓库里拿,这种方式不是更好吗,这就是状态机的作用,在React
中有Redux
,Mobx
等一些库,而在Vue
中,官方推出的Vuex
是最适合Vue的,Vuex
中的状态是响应式的,完美的结合了Vue
的优势;
Vuex核心内容
在Vuex
对象中,其实不止有state
,还有用来操作state
中数据的方法集,以及当我们需要对state
中的数据需要加工的方法集等等成员。
成员列表:
state
存放状态getters
加工state
成员给外界mutations
同步state
成员操作actions
异步操作modules
模块化状态管理
Vuex工作流程
官网给出的流程图
首先,Vue
组件如果调用某个Vuex
的方法过程中需要向后端请求时或者说出现异步操作时,需要dispatch
Actions
的方法,以保证数据的同步。可以说,action
的存在就是为了让mutations
中的方法能在异步操作中起作用。
如果没有异步操作,那么我们就可以直接在组件内提交状态中的Mutations中自己编写的方法来达成对state
成员的操作。但是不建议这样做,我们希望总是由action
去调用mutations
修改state
不建议在组件中直接对state
中的成员进行操作,这是因为直接修改(例如:this.$store.state.name = 'hello'
)的话不能被VueDevtools
所监控到。同样,总是应该有mustaions
去修改state
最后被修改后的state
成员会被渲染到组件的当中去。
这样就形成了一个单向闭环,这样的好处是可以明确的知道数据的流向,避免数据混乱;
用法
安装
npm install vuex --save
或者
yarn add vuex
代码组织
一般我们把全局状态单独放在一个文件夹中,然后把store
导出,在main.js
中使用这个store
,store
文件夹组织类似于
store
的index.js
文件类似于下面代码,由于Vuex
实例化之前需要Vue.use()
一下,因此需要先导入vue
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
import state from './state'
import mutations from './mutations'
import getters from './getters'
import actions from './actions'
// 单独模块
import users from './modules/users'
export default new Vuex.Store({
state,
mutations,
actions,
getters,
modules: {
users
}
})
main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'
new Vue({
store,
render: h => h(App)
}).$mount('#app')
state & mapState
state
的声明很简单,就是一个对象,里面存放的是响应式数据
state.js
export default {
name: 'jerry',
age: 18
}
由于我们把state
抽离出来了,因此直接导出对象就行了
数据声明了,怎么使用呢? 在组件中我们直接可以拿到store
里的state
,有以下几种方式
直接在模板中使用
<template> <div> {{ $store.state.name }} {{ $store.state.age }} </div> </template>
computed
中指定当不使用
mapState
辅助函数的时候,直接定义计算属性,使用this.$store.state
获取状态,如果很多状态,这样写代码十分冗余但是如果需要拿到状态和本地数据进行结合,这样写是很方便的,(无法使用过滤器,过滤器中无法访问实例this)
<template> <div> {{ name }} {{ age }} </div> </template> <script> import { mapState } from 'vuex' export default { // ... computed: { name() { return this.$store.state.name }, age() { return this.$store.state.age + this.localData } } } </script>
mapState
辅助函数mapState
的第一个参数是命名空间,字符串,结合modules
使用,可省略,第二个参数可以是一个数组或者对象<template> <div> {{ name }} {{ age }} </div> </template> <script> import { mapState } from 'vuex' export default { // ... computed: { ...mapState(['name', 'age']) } } </script>
如果传入对象,必须定义一个计算属性名称,不能使用ES6的
{name, age}
的省略写法,否则报错<template> <div> {{ stateName }} {{ stateAge }} </div> </template> <script> import { mapState } from 'vuex' export default { // ... computed: { ...mapState({ stateName: 'name', stateAge: 'age' }) } } </script>
getters & mapGetters
getter
可认为是state
的计算属性,将state
作为第一个参数,可以拿到state
里的数据,并且还可以接受第二个参数,就是getters
,用来调用其他getter
getters.js
export default {
// getter将state作为第一个参数
ageWithSuffix(state) {
return state.age + '岁'
},
// getter也可将getters作为第二个参数,用来调用其他getter
ageWithPrefix(state, getters) {
return '今年' + getters.ageWithSuffix
}
}
如何使用getters?
模板里直接使用
<template> <div> {{ $store.getters.ageWithSuffix }} {{ $store.getters.ageWithPrefix }} </div> </template>
computed
里使用<template> <div> {{ prefix }} </div> </template> <script> export default { // ... computed: { prefix(){ return this.$store.getters.ageWithPrefix } } } </script>
mapGetters
辅助函数
数组写法<template> <div> {{ ageWithSuffix }} {{ ageWithPrefix }} </div> </template> <script> import { mapGetters } from 'vuex' export default { // ... computed: { ...mapGetters(['ageWithSuffix', 'ageWithPrefix']), } } </script>
对象写法
<template> <div> {{ suffix }} {{ prefix }} </div> </template> <script> import { mapGetters } from 'vuex' export default { // ... computed: { ...mapGetters({ suffix: 'ageWithSuffix', prefix: 'ageWithPrefix' }) } } </script>
上面是定义状态以及获取状态的方法,而修改状态需要dispatch
一个action
,然后再由action
去commit
一个mutation
,实现状态修改
所以action
和mutation
之间是强耦合的,我们为了去耦合,会引入一个mutation-type.js
,这个也可以帮助我们快速找到相关代码的位置。
mutation-type.js
export const CHANGE_AGE = 'change_age'
export const CHANGE_NAME = 'change_name'
然后我们定义action
和mutation
的时候,通过这个中间变量,进行关联;
actions & mutations
actions
action
整体是一个对象,key
是将来我们dispatch``action
的type
值,因此我们可以直接使用mutation-types
导出的值,和mutaion
保持一致。action
函数第一个参数是一个与store
实例具有相同方法和属性的context
对象,因此你可以调用context.commit
提交一个mutation
,或者通过context.state
和context.getters
来获取state
和getters
,甚至可以通过context.dispatch
派发另外一个action
,可以通过解构来简化代码action
里是可以执行异步代码的,等异步执行完了再去commit
mutation
,此时才是同步修改state
actions.js
import { CHANGE_AGE, CHANGE_NAME } from './mutation-types' export default { // 第二个参数是payload,用于接收数据,通常为一个对象,用于接收多个数据,也可以直接接受单个参数 [CHANGE_NAME]({ commit }, payload) { commit(CHANGE_NAME, payload) }, }
如果我们利用
async / await
,我们可以如下组合action
,在action
中调用另外一个action
// 假设 getData() 和 getOtherData() 返回的是 Promise async actionA({ commit }) { commit('gotData', await getData()) }, async actionB({ dispatch, commit }) { await dispatch('actionA') // 等待 actionA 完成 commit('gotOtherData', await getOtherData()) }
mutations
mutation
接收两个参数,一个是state
,用于修改状态,一个是payload
,用于接收action
传参,payload
大多数为一个对象,用以传递多个数据mutations.js
import { CHANGE_AGE, CHANGE_NAME } from './mutation-types' export default { // [CHANGE_AGE] 为es6语法,可以将表达式作为属性名 [CHANGE_AGE](state, payload) { console.log(payload); state.age = payload.age }, [CHANGE_NAME](state, payload) { state.name = payload.name }, }
$store.dispatch & mapActions
$store.dispatch
$store.dispatch
可以直接派发一个action
,dispatch
接收一个对象作为参数,对象必须指定一个type
属性,表示派发哪一个action
,或者把type
放在第一个参数上<template> <div> <button @click="changeName">修改名字</button> </div> </template> <script> import { CHANGE_NAME } from '@/store/mutation-types' export default { // ... methods: { changeName() { this.$store.dispatch({ type: CHANGE_NAME, name: 'tom' }) // 把type放在第一个参数上 this.$store.dispatch(CHANGE_NAME, { name: 'tom' }) } } } </script>
mapActions
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')`,相当于取了个别名 }) } }
引入
mutation-types
的例子:<template> <div> <button @click="changeName">修改名字</button> </div> </template> <script> import { CHANGE_NAME } from '@/store/mutation-types' import { mapActions } from 'vuex' export default { // ... methods: { ...mapActions([CHANGE_NAME]), changeName() { this[CHANGE_NAME]({name: 'tom'}) } } } </script>
$store.commit & mapMutations
$store.commit
我们是可以直接跳过
action
直接提交一个mutation
的,但是不建议这么做,即使你知道并没有异步操作$store.commit
可以直接提交一个mutation
,commit
接收一个对象作为参数,对象必须指定一个type
属性,表示提交哪一个mutation
,或者把type
放在第一个参数上<template> <div> <button @click="changeAge">修改年龄</button> </div> </template> <script> import { CHANGE_NAME, CHANGE_AGE } from '@/store/mutation-types' export default { // ... methods: { changeAge() { this.$store.commit({ type: CHANGE_AGE, age: 20 }) // 把type放在第一个参数上 this.$store.commit(CHANGE_AGE, { age: 20 }) } } } </script>
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')` }) } }
引入
mutation-types
的例子:<template> <div> <button @click="changeAge">修改年龄</button> </div> </template> <script> import { CHANGE_AGE } from '@/store/mutation-types' import { mapMutations } from 'vuex' export default { // ... methods: { ...mapMutations([CHANGE_AGE]), changeAge() { this[CHANGE_AGE]({age: 20}) } } } </script>
modules
在外面使用state
时,通过 {{$store.state.users.username}}
获取,
这里的users
是根据new Vuex.Store({ modules: { users: users } })
的key
来指定的
是否启用命名空间对模块内的state
都没有影响,取值的方式都需要加上命名空间
因此取值方式变成了
<template>
{{$store.state.users.username}}
</template>
或者
...mapState('account', ['user'])
或者
...mapState({
tomRole: state => state.users.role,
tomAge: state => state.users.age
})
如果仅仅是区分了模块,而没有设置命名空间,那么getters
、actions
、mutations
将会和根状态合并,外部直接获取,不需要加上命名空间
如果设置命名空间为true
,则在外面获取时需要加上命名空间,如下
如果使用命名空间,那么使用getters
、actions
、mutaions
都需要使用命名空间的写法,
[this.]$store.getters['account/isAdmin'] 或 ...mapGetters('account', ['isAdmin'])
[this.]$store.dispatch('account/login') 或 ...mapActions('account', ['login'])
[this.]$store.commit('account/login') 或 ...mapMutations('account', ['login'])
export default {
namespaced: true,
// 可以注意到state是一个函数返回了对象,避免自己和其他state数据互相污染,实际上和Vue组件内的data是同样的问题
state() {
return {
username: 'Tom',
role: 'admin',
age: 20
}
},
// 在这个模块的 getter 中,`getters` 被局部化了, 你可以使用 getter 的第四个参数来调用 `rootGetters`
// 如果你希望使用全局 state 和 getter,rootState 和 rootGetters 会作为第三和第四参数传入 getter,
// 也会通过 context 对象的属性传入 action。
getters: {
role(state, getters, rootState, rootGetters) {
return state.role
},
allAge(state, getters, rootState, rootGetters) {
return state.age + rootState.age
}
},
// 对于模块内部的 action,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState:
actions: {
change_role({ commit, rootState }) {
commit('m_change_role')
},
// 在这个模块中, dispatch 和 commit 也被局部化了,他们可以接受 `root` 属性以访问根 dispatch 或 commit
// 若需要在全局命名空间内分发 action 或提交 mutation,将 { root: true } 作为第三参数传给 dispatch 或 commit 即可。
someAction({ dispatch, commit, getters, rootGetters }) {
getters.someGetter // -> 'users/someGetter'
rootGetters.someGetter // -> 'someGetter'
dispatch('someOtherAction') // -> 'users/someOtherAction'
dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'
commit('someMutation') // -> 'users/someMutation'
commit('someMutation', null, { root: true }) // -> 'someMutation'
},
// 若需要在带命名空间的模块注册全局 action,你可添加 root: true,并将这个 action 的定义放在函数 handler 中。例如:
globalAction: {
root: true,
handler(namespacedContext, payload) { }
}
},
mutations: {
m_change_role(state, payload) {
state.role = 'vip'
}
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 289211569@qq.com