通过实例理解vuex中的模块(module)和命名空间(namespaced)

序言

使用Vue开发项目时,如果项目比较复杂的话,使用Vuex进行状态管理会大大简化开发难度,提高开发效率。

但是当使用Vuex的一些复杂功能(如:模块、命名空间)时,Vuex会变的稍微有些复杂。再加上Vuex的灵活性,同样的功能可以有很多种实现方式。因为太灵活、实现方式太多,导致经常会记混掉。所以就整理了一下Vuex的几种常用的使用方式以实例作个对比。真正实现拿来主义精神,方便后期开始使用。

一、基础概念

1.1、Vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。

1.2、vuex中的模块(module)

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块。

1.3、模块的命名空间

默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。

如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。

以下我们用例子来看一下不同情况下的区别。

二、vuex的常规用法(不使用模块(module)的store)

2.1、创建一个简单的store

store/index.js的内容

import Vue from 'vue'
import Vuex from 'vuex'
import api from '@/api/index'

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    loading : false, // 是否加载中(接口请求时的全屏loading效果)
    userInfo: {} // 用户信息
  },
  getters: {
    loading: state => state.loading,
    userInfo: state => state.userInfo, 
    userName: state => stete.userInfo.userName || ''
  },
  mutations: {
    setLoading(state, payload) {
      state.loading = payload
    },
    setUserInfo(state, payload) {
      state.userInfo = payload
    }
  },
  actions: {
    // 获取用户信息
    queryUserInfoAction({ commit }) {
      api.queryUserInfo().then((res) => {
        let result = res && res.result || {}
        commit('setUserInfo', result)
      })
    }
  }
})

export default store

JavaScript

Copy

2.2、在组件中使用

import { mapGetters, mapActions } from 'vuex'

export default {
  computed: {
    // 【传统方式】获取store中的数据
    /*
    loading() {
      return this.store.getters.loading
    },
    userName() {
      return this.store.getters.userName
    },
    */
    // 【辅助函数方式】获取store中的数据(代码更简洁)
    ...mapGetters(['loading', 'userName']),
  },
  created() {
    // 如果没有用户名,则查询用户信息,已有则不需查询(减少不必要的http请求)

    // 【传统方式】请求异步数据
    !this.userName && this.store.dispatch('queryUserInfoAction')

    // 【辅助函数方式】请求异步数据
    !this.userName && this.queryUserInfoAction()
  },
  methods: {
    ...mapActions(['queryUserInfoAction']), // 查询用户信息的aciton
    // 打开loading效果
    openLoading() {
        this.store.commit('setLoading', true)
    },
    // 关闭loading效果
    closeLoading() {
        this.$store.commit('setLoading', false)
    }
  }
}

JavaScript

Copy

三、vuex中模块的基础使用(不开启命名空间时)

默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。

对于action、mutation 和 getter,是在模块中还是在全局中,它们的使用方式是相同的,只是state会有所不同,模块中的state会多一层模块名。格式变成store.state.模块名.状态名(根state中的格式为store.state.状态名)。

3.1、目录结构

├── api             # api请求目录
│   ├── index.js        # 所有api请求
├── store           # store目录
│   ├── modules     # store中的所有模块
│   │   ├── theme.js    # 主题模块
│   │   ├── product.js  # 产品模块
│   ├── index.js        # store主文件

3.2、创建一个带模块的store

store主文件(store/index.js)内容

import Vue from 'vue'
import Vuex from 'vuex'
import api from '@/api/index'

import themeModule from './modules/theme.js' // 主题模块
import productModule from './modules/product.js' // 产品模块

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    loading : false, // 是否加载中(接口请求时的全屏loading效果)
    userInfo: {} // 用户信息
  },
  getters: {
    loading: state => state.loading,
    userInfo: state => state.userInfo, 
    userName: state => stete.userInfo.userName || ''
  },
  mutations: {
    setLoading(state, payload) {
      state.loading = payload
    },
    setUserInfo(state, payload) {
      state.userInfo = payload
    }
  },
  actions: {
    // 获取用户信息
    queryUserInfoAction({ commit }) {
      api.queryUserInfo().then((res) => {
        let result = res && res.result || {}
        commit('setUserInfo', result)
      })
    }
  },
  modules: {
    themeModule, // 主题模块
    productModule, // 产品数据
  }
})

export default store

JavaScript

Copy

主题模块(store/modules/theme.js)内容

import api from '@/api/index'

const themeModule = {
  state: {
    themeObj: {} // 主题数据
  },
  getters: {
    themeData: state => {
      return {
        primaryColor: '#fe3132', // 主颜色(如:按钮背景颜色)
        subBgColor: '#fff6f6', // 次要颜色(如:浅色背景色)
        ...state.themeObj // 没有返回数据使用以上默认值,有则覆盖以上数据
      }
    }
  },
  mutations: {
    setTheme(state, payload) {
      state.themeObj = payload
    }
  },
  actions: {
    queryThemeAction({ commit }) {
      return api.queryTheme().then(res => {
        let data = (res && res.result) || {}
        commit('setTheme',  data)
      })
    }
  }
}
export default themeModule

JavaScript

Copy

产品模块(store/modules/product.js)内容

import api from '@/api/index'

const productModule = {
  state: {
    proData: {}, // 产品数据
    indexDataRes: {} // 产品首页数据
  },
  getters: {
    proName: state => stete.proData.proName || '', // 产品名称
    proDesc: state => stete.proData.proDesc || '' // 产品描述
    indexData: state => stete.indexDataRes // 产品首页数据
  },
  mutations: {
    // 设置产品数据
    setProData(state, payload) {
      state.proData = payload
    },
    // 设置产品首页数据
    setIndexData(state, payload) {
      state.indexDataRes = payload
    },
  },
  actions: {
    // 获取产品数据
    queryProDataAction(context) {
      return api.queryProData().then(res => {
        // 页面数据
        let data = (res && res.result) || {}
        context.commit('setProData',  data)
      })
    },
    // 获取首页数据
    queryIndexDataAction(context) {
      return api.queryIndexData().then(res => {
        let data = (res && res.result) || {}
        context.commit('setIndexData',  data)
      })
    }
  }
}
export default productModule

JavaScript

Copy

3.3、在组件中使用

import { mapState, mapGetters, mapActions } from 'vuex'

export default {

  computed: {
    // 【传统方式】获取store中的数据
    // 注意state和getter数据结构的区别(state需要带上模块名,而getter不需要模块名)
    /*
    proData() {
        return this.store.state.productModule.proData
    },
    themeData() {
      return this.store.getters['themeData']
    },
    proName() {
      return this.store.getters['proName']
    },
    proDesc() {
      return this.store.getters['proDesc']
    },
    indexData() {
      return this.store.getters['indexData']
    },
    */

    // 【辅助函数方式】获取store中的数据(代码更简洁)
    ...mapState({ proData: state => state.proData }),
    ...mapGetters(['themeData']),
    ...mapGetters(['proName', 'proDesc', 'indexData'])
  },
  created() {
    // 【传统方式】获取异步数据
    // this.store.dispatch('queryThemeAction') // 获取主题数据
    // this.store.dispatch('queryProDataAction') // 获取产品数据
    // this.store.dispatch('queryIndexDataAction') // 获取首页数据

    // 【辅助函数方式】获取异步数据(需要先在methods中使用mapActions定义方法)
    this.queryThemeAction() // 获取主题数据
    this.queryProDataAction() // 获取产品数据
    this.queryIndexDataAction() // 获取首页数据
  },
  methods: {
    ...mapActions(['queryThemeAction']),
    ...mapActions(['queryProDataAction', queryIndexDataAction])
  }
}

JavaScript

Copy

四、vuex中【开启命名空间】的模块

如果你想模块之间相互独立、互不影响。可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。

当模块被注册后,它的所有 getter、action 和 mutation 都会自动根据模块注册的路径调整命名。所以开启命名空间的模块中的getter、action 和 mutation的使用方式都会改变。

但是开启命名空间和不开启命名空间的模块中的state的使用方式不会改变。格式依然是store.state.模块名.状态名

4.1、开启模块的命名空间

const moduleName = {
  state: {
    // 此处代码省略...
  },
  getters: {
    // 此处代码省略...
  },
  mutations: {
    // 此处代码省略...
  },
  actions: {
    // 此处代码省略...
  },
  namespaced: true // 开启命名空间
}
export default moduleName

JavaScript

Copy

4.2、在组件中使用

import { mapState, mapGetters, mapActions } from 'vuex'

export default {

  computed: {
    // 【传统方式】获取store中的数据
    /*
    proData() {
        return this.store.state.productModule.proData
    },
    themeData() {
      return this.store.getters['themeModule/themeData']
    },
    proName() {
      return this.store.getters['productModule/proName']
    },
    proDesc() {
      return this.store.getters['productModule/proDesc']
    },
    indexData() {
      return this.store.getters['productModule/indexData']
    },
    */

    // 【辅助函数方式一】获取store中的数据(代码更简洁)
    /*
    ...mapState({ proData: state => state.productModule.proData }),
    ...mapGetters(['themeModule/themeData']),
    ...mapGetters(['themeModule/proName', 'themeModule/proDesc', 'themeModule/indexData'])
    */

    // 【辅助函数方式二】获取store中的数据(代码最简洁)
    // 将模块的空间名称字符串作为第一个参数传递给辅助函数,这样所有绑定都会自动将该模块作为上下文。
    ...mapState('productModule', { proData: state => state.proData }),
    ...mapGetters('themeModule', ['themeData']),
    ...mapGetters('productModule', ['proName', 'proDesc', 'indexData'])
  },
  created() {
    // 【传统方式】获取异步数据
    // this.store.dispatch('themeModule/queryThemeAction') // 获取主题数据
    // this.store.dispatch('productModule/queryProDataAction') // 获取产品数据
    // this.store.dispatch('productModule/queryIndexDataAction') // 获取首页数据

    // 【辅助函数方式】获取异步数据(需要先在methods中使用mapActions定义方法)
    this.queryThemeAction() // 获取主题数据
    this.queryProDataAction() // 获取产品数据
    this.queryIndexDataAction() // 获取首页数据
  },
  methods: {
    ...mapActions('themeModule', ['queryThemeAction']),
    ...mapActions('productModule', ['queryProDataAction', queryIndexDataAction])
  }
}

JavaScript

Copy

想了解更多关于vuex及模块相关内容,请阅读vuex官网

扩展阅读

  1. vuex官网 Module
  2. vuex多种使用方式和map辅助函数
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容