通过实例理解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辅助函数

你可能感兴趣的:(通过实例理解vuex中的模块(module)和命名空间(namespaced))