废话先说前面:
这几天在写一个vuex模块化的demo,感觉知识都忘得差不多了,刚刚写完的demo,还热乎着,就想写一个分享的博客,话不多说,我们开始:
步入正题:
vuex是什么?
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。(官话哈),我个人觉得可以这样理解,当没有一个这样的一个工具的时候,如果我们有一些数据是公用的,你会怎么办?
1、组件传值,是可以的,但是当你的组件嵌套太深,或者公用数据不只几个组件使用,而是很多组件的时候,请相信我,你会传的怀疑人生。也不要尝试,会吐!
2、建一个放公用数据的文件,比如globalData.vue,然后在main.js里面引用他,然后就可以在其他组件里面使用它的数据了,那我们想改变公用数据怎么办,我们可以在组件内部使用方法去改变他,但是为了项目更有模块化,建议是在golbalData.vue内部定义方法去改变他,你可能会问这种方式不好吗? 好!当然好!,但是前提是你的公用数据不是特别特别多的时候,这样的方法其实是最好的,其实这就是vue官网所提到的store模式,而且他也推荐你在公用数据不多时使用store模式,当你的项目不够大,公用数据不多的时候使用vuex其实是很恶心人的。
3、因此当你的项目足够大,公用数据足够多,比如一个企业级项目的时候,你就可能会需要一个在组件外能够很好地来管理你的这些庞大的数据的工具,这个工具就是vuex。
通过这张图,我们需要认识到vuex里面的数据是单向流动的,跟vue的双向数据流是不一样,vuex的数据是存放在state里面的,只能通过mutation来改变。其他的我们下面会介绍。
开始项目实践(代码里面的注释也很重要,是我列举的不同的使用方法)
1、安装vuex
npm install vuex --save // npm
yarn add vuex // yarn
2、引用
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
3、创建一个store文件夹,这次主要讲的是vuex的模块化使用,我的文件结构是这样子的:
我这里是个demo,所以把mutationsTypes.js放在外面了,也可以放在modules里面的模块文件内来定义types(官网说的是mutations的类型,其实也就是mutations的事件名称)。
外面的actions和mutations是根actions和根mutations,可以定义一些公共的方法来使用。
来看modules里面的student模块,里面包含state、getter、mutations、actions。
4、挂载
store文件下的index.js文件
import Vue from 'vue';
import Vuex from 'vuex';
import Student from './modules/student';
import Teacher from './modules/teacher';
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
Student,
Teacher
}
});
main.js文件
import store from '../store';
new Vue({
el: '#app',
i18n,
router,
store, // 挂载
components: { App },
template: '<App/>'
});
一定不要忘记挂载,一定不要忘记挂载!!!
state(以student模块为实例)
state里面是student模块内存储数据的地方,类比组件内的data ,
在getter、mutations、actions中可能会用到的数据,最好在这里都提前定义一下。
state.js的代码:
export default {
name: '张三',
age: 16
};
mapState
辅助函数
import { mapState } from 'vuex'
// state一般映射到组件内的computed里面
computed: {
/* ...mapState({ // 返回的是对象 // 不使用命名空間的用法,更多請查看官网
name: state => state.Student.name, // 注意不使用命名空间的时候,后面要跟模块名字
age: state => state.Student.age
}), */
// 使用对象展开运算符将此对象混入到外部对象中
...mapState('Student', { // 当你需要重新命名的时候,可以使用返回对象 // 使用命名空间的用法,更多用法查看官网
studentName: 'name', // 传字符串参数 'name' 等同于 `state => state.name`
age: 'age'
}),
/* ...mapState('Student', [ // 当不重新命名的时候,可以使用返回数组 // 使用命名空间的用法,更多用法查看官网
'name',
'age'
}), */
},
使用方法,就跟data里面的普通数据一样,直接使用,js内使用this.age获取,html内使用{{age}}即可。
getters
类比组件内部的计算属性computed,可以在这里面对数据进行一些简单的运算操作、过滤等。
getter可以接收两个参数,第一个参数是state(如果是模块化,这个state默认就是跟他在一个模块内的state,不需要在getters.js里面引入,他可以自己识别,这个在module里面会做详细说明。),第二个参数可以接收另外一个getters,使用里面的方法。
const getters = {
changeAge (state) {
return state.age++;
},
computeAge: (state) => (age) => {
// 可以让getter返回一个函数,来实现给getter传参
return state.age + age;
}
};
export default getters;
// 接收另一个getters的官网实例,这个我没有写成demo的内容,可自行尝试
getters: {
// ...
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
mapGetters
辅助函数
import { mapGetters } from 'vuex';
// getter也是映射到computed里面,都是返回一个数据,很好理解
computed: {
...mapGetters('Student', [ // 使用命名空间的用法
'changeAge',
'computeAge'
]) // 当不需要另外取名字的时候,可以返回数组
// ...mapGetters({ // 当需要变更名字的时候,可以返回对象 // 没有使用命名空间的用法
// alterAge: 'changeAge' // 把 `this.alterAge` 映射为`this.$store.getters.changeAge`
// })
},
使用方法:跟组件内computed下的方法使用方式一样
<template>
<div class="">
<div>{{changeAge}}</div>
<div>{{computeAge(2)}}</div> // 能够传参的getter
</div>
</template>
mutations
还记得上面的单向数据流的时候说过更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数,载荷(除了state额外的参数)作为第二个参数。
我的demo里面的时间类型是已经定义过的,在mutationsType.js文件内
mutationsType.js:
export const SET_STUDENT_INFO = 'set_student_info';
export const SET_STUDENT_INFO_ACTION = 'set_student_info_action';
使用常量替代 mutation 事件类型在各种 Flux 实现中是很常见的模式。用不用常量取决于你——在需要多人协作的大型项目中,这会很有帮助。但如果你不喜欢,你完全可以不这样做。
mutations.js:
// mutation必须是同步函数
import { SET_STUDENT_INFO, SET_STUDENT_INFO_ACTION } from '../../mutationsTypes';
const mutation = {
[SET_STUDENT_INFO] (state, payload) { // payload载荷
state.age += payload.age;
},
[SET_STUDENT_INFO_ACTION] (state, payload) {
state.name = state.name + payload.name;
}
};
export default mutation;
mapMutations辅助函数
组件内
import { mapMutations } from 'vuex'
mutations映射到methods内
methods: {
...mapMutations('Student', [SET_STUDENT_INFO]), // 不使用命名空间的时候,去掉'Student'即可
handleTrigger () { // 定义的组件内调用mutations的方法
const obj = { age: 20 }; // 要传给mutations的载荷
this[SET_STUDENT_INFO](obj); 将 `this.set_student_info(obj)` 映射为 `this.$store.commit('set_student_info',obj)`
},
}
actions
actions里面是异步的方法,他类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作
actions同样接收两个参数,一个是context,一个是载荷(额外的参数);context 与 store 实例具有相同方法和属性的,因此可以调用 context.commit
提交一个 mutation,或者通过 context.state
和 context.getters
来获取 state 和 getters。
这里需要注意的是如果是模块化的vuex,这里的context.state和context.getter指的是当前模块内的state和getter,当然为了使我们能够拿到其他模块内的state和getter,context内还有两个属性,rootState和rootGetters就可以拿到整个store内的数据了,是不是很方便。
actions.js
import { SET_STUDENT_INFO_ACTION } from '../../mutationsTypes';
const action = {
async setInfoAsync ({ commit }, payload) { // 异步调用mutation,使用载荷传参
console.log(payload);
setTimeout(() => {
commit(SET_STUDENT_INFO_ACTION, payload);
}, 1000);
}
};
export default action;
mapActions辅助函数
actions映射到methods
methods: {
...mapActions('Student', ['setInfoAsync']), // 使用命名空间的用法
handleTriggerA () {
const obj1 = { name: 'flower' };
this.setInfoAsync(obj1); 将 `this.setInfoAsync(amount)` 映射为 `this.$store.dispatch('setInfoAsync', obj1)`
}
}
另外组合actions大家也可以了解一下,使用async和await
最后的最后就是在student里面的index.js里面引用一下这些模块了。
import state from './state';
import getters from './getters';
import mutations from './mutations';
import actions from './actions';
export default {
namespaced: true, // 开启命名空间,注意这里是namespaced不是namespace,很多人会写错
state,
getters,
mutations,
actions
};
开启命名空间,只有他为true的时候,命名空间才需要开启,开启不开启命名空间,在使用辅助函数时,用法也是不太一样的,上面都已经写了,其实也就是map的时候,需要加一个命名空间的名字,让组件知道,你这是哪个模块里面的方法。
好了,基本就是这些!!