加深了解vuex及使用typescript如何搭建store文件夹

自从使用vue作为项目开发框架后,vue的全家桶每天都会与我见面,其中vuex更是常客。对于专门作为vue的状态管理模式,我认为这应该是每一位前端从业者都必须深入掌握的一项技能,可是在这段时间的面试中,我问了一些vuex相关的知识,却很少有人回答全面,即使他们的简历上写了3-4年的从业经验。比如以下这些vuex最基础的问题。

  • 如何调用store.js里的MutationActions里定义的方法?

    大部分人回答的都是以通过this.$store.commit或者this.$store.dispatch这种通过调用vue实例中的$store的方式,而且只知道这一种调用方法。其实还有两种方式:1.通过import store from 'store.js'的方式引入文件,通过store.commitstore.dispatch的方式调用。2.使用mapActions,mapMutation等辅助函数将组件的methods映射为store对应的方法名。

  • 使用过vuex的module概念吗?在.vue文件中如何使用module里的MutationActions里定义的方法?

    这个基本上就全军覆没了,仅知道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结构图
vuex的五个核心概念State,Getter,Mutation,Action,Module,基本用法我就不再一一列举了,参考vue官网一定比我介绍的详细。


以下只介绍一些我个人对这几个概念中比较少用的知识点的罗列。

State
  • 如何获取模块内的state
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import {mapState} from 'vuex';
    export default {
    computed:{
    ...mapState([{
    count:state=>state.moduleName.count
    }])
    }
    }
    or
    this.$store.state.moduleName.count
Module
  • 如何使用mapState, mapGetters, mapActions 和 mapMutations 这些函数来绑定带命名空间的模块。

在辅助函数第一个参数可以填模块名

1
2
...mapGetters('moduleName',['foo','bar'])
...mapActions('moduleName',['fn1','fn2'])

使用 createNamespacedHelpers 创建基于某个命名空间辅助函数

1
2
3
const { mapState, mapActions } = createNamespacedHelpers('moduleName')
...mapGetters('moduleName',['foo','bar'])
...mapActions('moduleName',['fn1','fn2'])

通过this.$store调用,比如现在模块名叫child

1
2
3
this.$store.getters['child/foo']
this.$store.commit('child/addCount',1)
this.$store.dispatch('child/fn',data)
  • 在带命名空间的模块内访问全局内容

    getters有四个参数state,getters,rootState,rootGetters,后面两个是全局内容。
    actions第一个参数是个object,他里面包含{ dispatch, commit, getters, rootGetters },最后一个是全局内容。
    在actions里如果要分发 action 或提交 mutation,将 { root: true } 作为第三参数传给 dispatch 或 commit 即可。

1
2
dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'
commit('someMutation', null, { root: true }) // -> 'someMutation'
  • 在带命名空间的模块注册全局 action
    若需要在带命名空间的模块注册全局 action,你可添加 root: true,并将这个 action 的定义放在函数 handler 中。
1
2
3
4
5
6
7
// module/child.js
actions: {
someAction: {
root: true,
handler ({ dispatch, commit, getters, rootGetters }, payload) { ... } // -> 'someAction'
}
}

使用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
    18
        import 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
    3
    export 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
    18
    import {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
    4
    export interface ListState {
    listArr ?: any[],
    foo ?: string
    }
  • module/list/getters.ts 暴露模块list的getters参数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import {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
    8
    import {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
    18
    import {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
    21
    import { 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'; //这里要注意,名称需要和模块暴露出来的参数名保持一致

    @Component({
    name:'todolist'
    })
    export default class ToDoList extends Vue{
    @State('list') list:any;
    @Getter('bar',{namespace}) bar:any;
    @Mutation('addFuHao',{namespace}) addFuHao:any;
    @Action('fetchData',{namespace}) fetchData:any; //引入模块list的action
    @Action('rootActionTestFn') rootActionTestFn:any; //引入全局action

    mounted() {
    console.log(this.bar)
    this.fetchData();
    this.addFuHao();
    }
    }
我想吃鸡腿!