自从使用vue作为项目开发框架后,vue的全家桶每天都会与我见面,其中vuex更是常客。对于专门作为vue的状态管理模式,我认为这应该是每一位前端从业者都必须深入掌握的一项技能,可是在这段时间的面试中,我问了一些vuex相关的知识,却很少有人回答全面,即使他们的简历上写了3-4年的从业经验。比如以下这些vuex最基础的问题。
如何调用store.js里的
Mutation
与Actions
里定义的方法?大部分人回答的都是以通过
this.$store.commit
或者this.$store.dispatch
这种通过调用vue实例中的$store
的方式,而且只知道这一种调用方法。其实还有两种方式:1.通过import store from 'store.js'
的方式引入文件,通过store.commit
和store.dispatch
的方式调用。2.使用mapActions
,mapMutation
等辅助函数将组件的methods映射为store对应的方法名。使用过vuex的
module
概念吗?在.vue
文件中如何使用module
里的Mutation
与Actions
里定义的方法?这个基本上就全军覆没了,仅知道vuex有这么一个概念,但项目中没有用过,所以对于模块化的store不了解。
对于vuex的module
概念,其实真的不难,个人觉得,即使项目中没有用过,但vuex作为我们日常开发中的老朋友,认真了解一下他5个孩子State,Getter,Mutation,Action,Module
,从时间成本上来说,也许一个晚上就可以搞定,即使现在项目中使用不到,那未来的项目再碰到至少不会措手不及吧。
从以上俩个基础问题可以看出,很多前端对vuex的学习并不深入,觉得能在项目中使用就可以了,毕竟代码不是浮现在页面上的东西,只要功能能正确实现,很少有人对你的代码会有兴趣。这怎么说呢,多少是有点道理的,但是对知识的追求不该止步于实现功能这一步,既然尤雨溪开源了vue这么好的前端框架,如果我们不更加深入了解vuex,是不是对别人的知识成果不尊重呢?
Vuex到底是什么东西?
在说vuex之前,可以先了解一下Flux与Redux。每一位前端对MVC框架与MVVM框架都不陌生,如果略显遗忘,可以参考阮一峰老师的对这几个框架的解释,简单来说Flux是一种架构思想,专门解决软件的结构问题,而Redux则是将Flux与函数式编程结合在一起,使收Flux启发的简介版Flux。其实这些概念都不重要,随便百度一下就好啦︿( ̄︶ ̄)︿
按照官方的说法Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
按照惯例放一张官方的图
vuex的五个核心概念State,Getter,Mutation,Action,Module
,基本用法我就不再一一列举了,参考vue官网一定比我介绍的详细。
以下只介绍一些我个人对这几个概念中比较少用的知识点的罗列。
State
- 如何获取模块内的state
1
2
3
4
5
6
7
8
9
10import {mapState} from 'vuex';
export default {
computed:{
...mapState([{
count:state=>state.moduleName.count
}])
}
}
or
this.$store.state.moduleName.count
Module
- 如何使用mapState, mapGetters, mapActions 和 mapMutations 这些函数来绑定带命名空间的模块。
在辅助函数第一个参数可以填模块名
1 | ...mapGetters('moduleName',['foo','bar']) |
使用 createNamespacedHelpers 创建基于某个命名空间辅助函数
1 | const { mapState, mapActions } = createNamespacedHelpers('moduleName') |
通过
this.$store
调用,比如现在模块名叫child
1 | this.$store.getters['child/foo'] |
在带命名空间的模块内访问全局内容
getters有四个参数
state,getters,rootState,rootGetters
,后面两个是全局内容。
actions第一个参数是个object,他里面包含{ dispatch, commit, getters, rootGetters }
,最后一个是全局内容。
在actions里如果要分发 action 或提交 mutation,将{ root: true }
作为第三参数传给 dispatch 或 commit 即可。
1 | dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction' |
- 在带命名空间的模块注册全局 action
若需要在带命名空间的模块注册全局 action,你可添加 root: true,并将这个 action 的定义放在函数 handler 中。
1 | // module/child.js |
使用Typescript开发的项目如何写vuex的store文件夹
这个就有意思啦,既然选择了ts代替了js,那么ts的一些特性就要使用起来,于是我写了一个demo。
下图是我demo的文件结构,有木有觉得分的很清晰,很详细,哈哈。其中list是一个模块。
接下来就一个一个文件看过去吧。
index.ts
store的对外暴露文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import Vue from 'vue'
import Vuex ,{StoreOptions}from 'vuex'
import {RootState} from './type';
import {todolist} from './module/list/index';
Vue.use(Vuex)
const store: StoreOptions<RootState> = {
state: {
version:'1.0.0'
}, //注册全局state
getters:{}, //注册全局getters
mutations: {}, //注册全局mutations
actions: {}, //注册全局actions
modules:{
todolist
}
}
export default new Vuex.Store<RootState>(store);
如果你对
StoreOptions<RootState>
这种写法有疑问,你可以参考vuex的源代码,在vuex/types/index.d.ts
文件中有相关介绍。并且你需要了解ts的泛型与类型变量。
type.ts
暴露全局state的接口,如果要新添state,要提前在这里定义类型,因为类型检查器会检查接口里的属性1
2
3export interface RootState {
version: string
}module/list/index.ts
store模块list的对外暴露文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import {Module} from 'vuex';
import {ListState} from './type';
import {getters} from './getters';
import {mutations} from './mutations';
import {actions} from './actions';
import {RootState} from '../../type'
const namespaced:boolean = true; //开启命名空间
export const state:ListState = {
listArr:[],
foo:'hello'
}
export const todolist:Module<ListState,RootState> = {
namespaced,
state,
getters,
mutations,
actions
}
如果你对
namespaced
有疑问,可以参看vue的官网对命名空间的解释,点我
module/list/type.ts
暴露模块list的state的接口。1
2
3
4export interface ListState {
listArr ?: any[],
foo ?: string
}module/list/getters.ts
暴露模块list的getters参数。1
2
3
4
5
6
7
8
9import {GetterTree} from 'vuex';
import {ListState} from './type';
import {RootState} from '../../type';
export const getters:GetterTree<ListState,RootState> = {
bar(state,getters,rootState,rootGetters): string {
return state.foo+' world'
}
}module/list/mutations.ts
暴露模块list的mutation参数。1
2
3
4
5
6
7
8import {MutationTree} from 'vuex';
import {ListState} from './type';
export const mutations:MutationTree<ListState>={
addFuHao(state,payload:string){
state.foo += payload
}
}module/list/actions.ts
暴露模块list的actions参数。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import {ActionTree} from 'vuex';
import {ListState} from './type';
import {RootState} from '../../type';
export const actions:ActionTree<ListState,RootState>={
fetchData({commit,dispatch,getters,rootGetters},payload):any{
setTimeout(()=>{
commit('addFuHao','!!!!')
},3000)
},
rootActionTestFn:{ //注册全局action
root:true,
handler(...a):any{
console.log('触发了rootActionTestFn')
console.log(a)
}
}
}
一般来说模块内是很少嵌套子模块的,除非项目特别要求。如果需要,照葫芦画瓢就好啦。
- 那么定义好了store,如何使用呢?主要使用的是
vuex-class
。参考以下代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21import { Component, Vue } from 'vue-property-decorator';
import {State, Getter ,Mutation,Action} from 'vuex-class';
import {ListState} from '../store/module/list/type'
const namespace:string = 'todolist'; //这里要注意,名称需要和模块暴露出来的参数名保持一致
({
name:'todolist'
})
export default class ToDoList extends Vue{
'list') list:any; (
'bar',{namespace}) bar:any; (
'addFuHao',{namespace}) addFuHao:any; (
'fetchData',{namespace}) fetchData:any; //引入模块list的action (
'rootActionTestFn') rootActionTestFn:any; //引入全局action (
mounted() {
console.log(this.bar)
this.fetchData();
this.addFuHao();
}
}