最全的 vuex 详解使用
前言
根据日常开发,整理了关于vuex的使用文档,附带一些网上主流的使用方式,适合刚入门的小白。
废话不多说,先上代码。
vuex 基础及 modules 模块化使用
目录结构:
store/
|- modules
|- moduleA.js
|- moduleB.js
|- action.js
|- getters.js
|- index.js
|- mutations-types.js
|- mutations.js
|- state.js
main.js
在 main 中引入使用 store
import store from './store/index.js';
Vue.prototype.$store = store;
const app = new Vue({
store,
render: (h) => h(App),
});
app.$mount('#app');
index.js
import Vue from 'vue';
import Vuex from 'vuex';
import state from './state';
import mutations from './mutations';
import getter from './getters';
import actions from './action';
// 模块管理
import moduleA from './modules/moduleA.js';
import moduleB from './modules/moduleB.js';
Vue.use(Vuex);
const store = new Vuex.Store({
state,
mutations,
getter,
actions,
modules: {
moduleA,
moduleB,
},
});
export default store;
state.js
const state = {
count = 0;
}
export default state;
mutation-types.js
//mutation-type(我们会将所有mutations的函数名放在这个文件里)
export const COUNTADD = 'COUNTADD'; // 递增count
export const COUNTRESET = 'COUNTRESET'; // 重置count
mutations.js
import * as types from './mutation-types';
const mutations = {
// payload 官方定义为载荷,本质就是执行时传入的参数
[types.COUNTADD](state, payload) {
state.count++;
},
[types.COUNTRESET](state, payload) {
state.count = payload;
},
};
export default mutations;
action.js
import * as types from './mutation-types';
const action = {
handleResetCount({ commit, dispatch, state, rootState }, payload) {
commit(types.COUNTRESET, 0);
},
};
getters.js
const getters = {};
export default getters;
modules
- moduleA
import Axios from 'axios';
export default {
// 命名空间
namespaced: true,
/* 存储数据 */
state: {
dataA: {},
},
/* 方法 */
mutations: {
handleDataA(state, payload) {
state.dataA = payload;
},
},
/* 计算属性 */
getters: {},
/* 异步方法,(如ajax) */
actions: {
async _todoA({ commit }, payload) {
const res = await Axios.get('apiA');
commit('handleDataA', res);
},
},
};
- moduleB
export default {
// 命名空间
namespaced: true,
/* 存储数据 */
state: {},
/* 方法 */
mutations: {},
/* 计算属性 */
getters: {},
/* 异步方法,(如ajax) */
actions: {},
};
组件调用
<template>
<div>
<button @click="clickTest">清空</button>
</div>
</template>
import { mapState, mapMutations, mapGetters, mapActions } from 'vuex';
export default {
data(){
return {
lists:[],
}
},
mounted(){},
methods: {
// 全局mutations调用 this.rootMutations()
...mapMutations(['rootMutations'])
// moduleA 为module的命名空间
// Mutations
...mapMutations('moduleA', [
handleDataA:'handleDataA', // 或者 'handleDataA'
]),
// Actions
...mapActions('moduleA', ['_todoA']), // this._todoA();
// lists 清空
clickTest(){
this.handleDataA([])
}
},
computed: {
// 全局state调用 this.rootState
...mapState(['rootState']),
// moduleA 为module的命名空间
...mapState('moduleA', ['dataA']),
...mapGetters('moduleA', []),
},
watch:{
dataA:{
handler(newVal){
this.lists = newVal
},
deep:true
}
}
};
vuex Mutation
只有 mutation 能动 State
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
const store = new Vuex.Store({
state: {
count: 1,
},
mutations: {
// 事件类型 type 为 increment
increment(state) {
// 变更状态
state.count++;
},
},
});
注意,我们不能直接 store.mutations.increment() 来调用,Vuex 规定必须使用 store.commit 来触发对应 type 的方法:
store.commit('increment');
传参
我们还可以向 store.commit 传入额外的参数:
mutations: {
increment (state, n) {
state.count += n
}
}
// 调用
store.commit('increment', 10)
mutation 中的这个额外的参数,官方给它还取了一个高大上的名字:载荷(payload)。
大多数情况下,载荷是一个对象,能够让我们更加易读:
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
关于提交的方式,有两种:
// 1、把载荷和type分开提交
store.commit('increment', {
amount: 10,
});
// 2、整个对象都作为载荷传给 mutation 函数
store.commit({
type: 'increment',
amount: 10,
});
修改规则
简单修改基础类型的状态数据倒是简单,没什么限制,但是如果修改的是对象,那就要注意了。比如这个例子:
const store = new Vuex.Store({
state: {
student: {
name: '小明',
sex: '女',
},
},
});
这个时候,我们如果想要给 student 添加一个年龄 age: 18 属性,怎么办呢?
是的,直接在 sex 下面把这个字段加上去不就行了,能这样当然最好了。但是如果我们要动态的修改呢?那就得遵循 Vue 的规则了。如下:
mutations: {
addAge (state) {
state.student = { ...state.student, age: 18 }
}
}
以上就是给对象添加属性的两种方式,当然,对于已添加的对象,如果想修改具体值的话,直接更改就是,比如 state.student.age=20 即可。
至于为什么要这样,之前我们了解过,因为 store 中的状态是响应式的,当我们更改状态数据的时候,监视状态的 Vue 组件也会自动更新,所以 Vuex 中的 mutation 也需要与使用 Vue 一样遵守这些规则。
使用常量
就是使用常量来替代 mutation 事件的名字。
// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION';
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'
const store = new Vuex.Store({
state: { ... },
mutations: {
// 使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
[SOME_MUTATION] (state) {
// mutate state
}
}
})
可能有人会有疑问啊,这样做到底有啥用,还得多创建个类型文件,用的时候还要导入进来,不嫌麻烦吗!
我们看看,mutation 是怎么调用的:store.commit('increment'),可以发现,这里 commit 提交的方法 increment,是以字符串的形式代入的。如果项目小,一个人开发的话倒还好,但是项目大了,编写代码的人多了,那就麻烦了,因为需要 commit 的方法一多,就会显得特别混乱,而且以字符串形式代入的话,一旦出了错,很难排查。
所以,对于多人合作的大项目,最好还是用常量的形式来处理 mutation,对于小项目倒是无所谓,想偷懒的随意就好。
必须是同步函数
一定要记住,Mutation 必须是同步函数。为什么呢?
前面说了,我们之所以要通过提交 mutation 的方式来改变状态数据,是因为我们想要更明确地追踪到状态的变化。如果像下面这样异步的话:
mutations: {
someMutation (state) {
api.callAsyncMethod(() => {
state.count++
})
}
}
我们就不知道什么时候状态会发生改变,所以也就无法追踪了,这与 Mutation 的设计初心相悖,所以强制规定它必须是同步函数。
store.commit('increment');
// 任何由 "increment" 导致的状态变更都应该在此刻完成。
参考链接:https://www.jianshu.com/p/64727454f151
vuex 调用其他 modules 的 state, actions, mutations, getters
由于 Vuex 使用了单一状态树,应用的所有状态都包含在一个大对象中。那么,随着应用的不断扩展,store 会变得非常臃肿。
为了解决这个问题,Vuex 允许我们把 store 分 module(模块)。每一个模块包含各自的状态、mutation、action 和 getter。
那么问题来了, 模块化+命名空间之后, 数据都是相对独立的, 如果想在模块 A 调用 模块 B 的state, actions, mutations, getters, 该肿么办?
moduleA:
export default {
// 命名空间
namespaced: true,
/* 存储数据 */
state: {
dataA: {},
},
/* 方法 */
mutations: {
handleDataA(state, payload) {
state.dataA = payload;
},
},
/* 计算属性 */
getters: {},
/* 异步方法,(如ajax) */
actions: {
async _todoA({ commit }, payload) {},
},
};
moduleB:
export default {
// 命名空间
namespaced: true,
/* 存储数据 */
state: {
dataB: {},
},
/* 方法 */
mutations: {
handleDataB(state, payload) {
state.dataB = payload;
},
},
/* 计算属性 */
getters: {},
/* 异步方法,(如ajax) */
actions: {
async _todoB({ commit }, payload) {},
},
};
1、假设模块 B 的 actions 里, 需要用模块 A 的 state 该怎么办?
actions: {
async _todoB({commit, dispatch, state, rootState }, payload) {
console.log(rootState) // 打印根 state
console.log(rootState.dataA) // 打印其他模块的 state
},
},
假设模块 B 的 actions 里, 需要调用模块 A(或根模块) 的 actions 该怎么办?
actions: {
async _todoB({commit, dispatch, state, rootState }, payload) {
dispatch('moduleA/_todoA',{},{root:true}) // 调用其他模块的 actions
dispatch('rootActions',{},{root:true}) // 调用根模块的 actions
},
},
如上代码中dispatch('moduleA/_todoA',{},{root:true})有 3 个参数, 第一个参数是其他模块的 actions 路径, 第二个是传给 actions 的数据, 如果不需要传数据, 也必须预留, 第三个参数是配置选项, 申明这个 acitons 不是当前模块的
假设模块 B 的 actions 里, 需要调用模块 A(或根模块) 的 mutations 该怎么办?
actions: {
async _todoB({commit, dispatch, state, rootState }, payload) {
commit('moduleA/handleDataA',{},{root:true}) // 调用其他模块的 mutations
commit('rootMutations',{},{root:true}) // 调用根模块的 mutations
},
},
假设模块 B 的 actions 里, 需要用模块 A 的 getters 该怎么办?
actions: {
async _todoB({commit, dispatch, state, rootState,rootGetters }, payload) {
console.log(rootGetters['moduleA/gettersFn']) // 打印其他模块的 getters
},
},
rootGetters 就是 vuex 中所有的 getters, 你可以用 rootGetters['xxxxx'] 来取其他模块的 getters
参考链接:https://segmentfault.com/a/1190000009434398
总结
以上就是关于vuex的整体使用,初学者可以慢慢消化。