Vuex模块化应用实践

作者: 我是你班主任 | 来源:发表于2019-03-01 18:21 被阅读173次

    Vuex作为Vue全家桶的成员之一,重要性肯定不用多说,正在做Vue项目的同学,随着项目需求、功能逐渐增加,用到Vuex也是早晚的事儿,作为一个前端,只能面对现实:学不动也得学!
    这篇文章主要介绍Vuex在大型项目中的模块化及持久化应用实践,下面正文开始

    Vuex的应用场景

    • 多个组件视图共享同一状态时(如登录状态等)
    • 多个组件需要改变同一个状态时
    • 多个组件需要互相传递参数且关系较为复杂,正常传参方式变得难以维护时
    • 持久化存储某些数据

    所以我们把组件共享的状态抽离出来,不管组件间的关系如何,都交给Vuex来处理

    组织store目录

    我们先按模块化的方式组织store目录,并在Vue根实例中注册store,Vuex 通过 store 选项,提供了一种机制将状态从根组件“注入”到每一个子组件中

    src
    ├── ...
    ├── main.js
    ├── App.vue
    └── store
        ├── index.js          # 我们组装模块并导出 store 的地方
        └── modules
            ├── product.js        # 产品模块
            ├── windowInfo.js    # 窗口信息模块
            └── user.js          # 登录模块
    

    src/store/index.js

    import Vue from 'vue'
    import Vuex from 'vuex'
    import user from './modules/user'
    import product from './modules/product'
    import windowInfo from './modules/windowInfo'
    
    Vue.use(Vuex)
    
    export default new Vuex.Store({
      modules: {
        // 注册modules中的模块
        user,
        product,
        windowInfo
      }
    })
    

    src/main.js

    import ...
    import store from './store' // 添加这行
    
    new Vue({
      el: '#app',
      router,
      store, // 注入到根实例
      template: '<App/>',
      components: { App }
    })
    

    store的属性

    • state(状态对象)
      state中存放多页面共享的状态字段
    • getters
      相当于当前模块state的计算属性
    • mutations
      如果想更新state中的字段,提交mutations中定义的事件是唯一的方式(key为事件名,value是一个函数),但是这个事件函数必须是同步执行的
    • actions
      可以定义异步函数,并在回调中提交mutation,就相当于异步更新了state中的字段
    vuex数据传递规则

    使用方法

    1. 把窗口的高度和宽度存到Vuex中,并且每当窗口被resize,state中的高度和宽度自动更新
      src/store/modules/windowInfo.js
    import { MU_WIN_RESIZE } from '../../common/constants'
    const windowInfo = {
      state: {
        // 初始化
        winHeight: 0,
        winWidth: 0
      },
      mutations: {
        // 这里把事件名统一抽离到constants.js统一管理,方便维护,避免重复。
        // 当然,你也可以不这么写。。
        // mutation事件接受的第一个参数是当前模块的state对象
        // 第二个参数是提交事件时传递的附加参数
        [MU_WIN_RESIZE] (state, payload) {
          const { winWidth, winHeight } = payload
          state.winWidth = winWidth
          state.winHeight = winHeight
        }
      },
      actions: {},
      getters: {}
    }
    
    export default windowInfo
    

    src/common/constants.js

    export const MU_WIN_RESIZE = 'MU_WIN_RESIZE' // 更新窗口尺寸
    

    下面打开项目的根组件添加监听resize事件和提交mutation事件逻辑
    src/App.vue

    <!--上面的template我就不往这儿放了-->
    <script>
      import { _on, _off, getClientWidth, getClientHeight } from './common/dom'
      import { MU_WIN_RESIZE } from './common/constants'
      import { mapMutations } from 'vuex'
    
      export default {
        name: 'app',
        data () {
          return {}
        },
        mounted () {
          this.handleResize()
          // 这里对addEventListener方法做了IE兼容处理,就不贴出来了,反正事件监听你们都会
          _on(window, 'resize', this.handleResize)
        },
        beforeDestroy () {
          _off(window, 'resize', this.handleResize)
        },
        methods: {
          // 对象展开运算符,不熟悉的同学该学学ES6了
          ...mapMutations({
            // 映射 this.winResize 为 this.$store.commit(MU_WIN_RESIZE)
            winResize: MU_WIN_RESIZE
          }),
          handleResize () {
            const winWidth = getClientWidth()
            const winHeight = getClientHeight()
            this.winResize({ winWidth, winHeight })
          }
        }
      }
    </script>
    

    到这一步,在拖动窗口触发‘resize’事件的时候,就会触发‘MU_WIN_RESIZE’这个mutation事件并把窗口宽高写入vuex,下面我们随便找个页面看能不能获取到我们写入的值

    <template>
      <div class="row">窗口高:{{winHeight}}  窗口宽:{{winWidth}}</div>
    </template>
    <script>
      import { mapState } from 'vuex'
      export default {
        name: 'test',
        data () {
          return {}
        },
        computed: {
          // 把state写入计算属性
          // 如果使用mapGetters也是写入计算属性
          ...mapState({
            winHeight: state => state.windowInfo.winHeight,
            winWidth: state => state.windowInfo.winWidth
          })
        },
      }
    </script>
    

    1. 有的时候我们会从后端获取一些下拉框选项的静态常量,而且很多页面都能用到,这个时候用Vuex是比较好的选择,涉及到后端获取,就要用到可以使用异步的actions了
      src/store/modules/product.js
    import {MU_PRODUCT_UPDATE_CONSTANTS} from '../../common/constants'
    
    const product = {
      state: {
        productConstants: []
      },
      mutations: {
        [MU_PRODUCT_UPDATE_CONSTANTS] (state, payload) {
          state.productConstants = payload
        }
      },
      actions: {
        // action函数第一个参数接受一个与 store 实例具有相同方法和属性的 context 对象,
        // 因此你可以调用 context.commit 提交一个 mutation,
        // 或者通过 context.state 和 context.getters 来获取 state 和 getters
        // 这里虽然能获取到state,但是不建议直接修改state中的字段
        async getProductConstants ({ commit }, payload) {
          try {
            // 请求接口,如果需要参数可以通过payload传递
            const res = await this.$api.product.getConstants()
            commit(MU_PRODUCT_UPDATE_CONSTANTS, res)
          } catch (e) {
            console.error(e)
          }
        }
      },
      getters: {}
    }
    
    export default product
    

    下面触发这个getProductConstants事件,触发这个action事件的位置需要注意一下,假设你有5个组件需要使用这个state,那就应该在这5个组件共同的父组件中调用一次action(找不到就在根实例中调用),然后在各个子组件中通过mapState或mapGetters获取state,千万不要每个组件使用前都调用一次action方法!
    src/App.vue

    <!--为了更直观的展示action,把之前的代码删掉了-->
    <script>
      import { mapActions } from 'vuex' // 注意是mapActions
    
      export default {
        name: 'app',
        data () {
          return {}
        },
        created () {
          // 触发请求
          this.getProductConstants()
        }
        methods: {
          ...mapActions([
            // 映射 this.getProductConstants 为 this.$store.dispatch('getProductConstants')
            'getProductConstants'
          ])
        }
      }
    </script>
    

    mapGetters, mapMutations, mapActions,这几个函数可以接受对象也可以接受数组作为参数,如果你需要在组件中以别的名字调用该事件(像上面的mapMutations)就可以传入对象,key为新命名,value是store中定义的名字;否则的话传数组就好了。


    那么问题来了,既然是异步操作,我想在操作结束后干点儿别的怎么做呢?
    很简单,调用action中的异步函数(this.$store.dispatch)返回的是一个Promise,如果你跟我一样用的是async await:

    <!--为了更直观的展示action,把之前的代码删掉了-->
    <script>
      import { mapActions } from 'vuex' // 注意是mapActions
    
      export default {
        name: 'app',
        data () {
          return {}
        },
        async created () {
          // 触发请求
          await this.getProductConstants()
          // 接下来执行的操作会等待上面函数完成才会执行
        }
        methods: {
          ...mapActions([
            // 映射 this.getProductConstants 为 this.$store.dispatch('getProductConstants')
            'getProductConstants'
          ])
        }
      }
    </script>
    

    如果你用的不是async await那就麻烦一点,在actions中定义事件的时候return一个new Promise,官方文档中有一个例子

    表单处理

    当你把从state中获取的字段填在v-model中时,如果用户修改表单数据,v-model会尝试直接修改store中的数据,这样做会有两个问题:

    1. 破坏了vuex的数据传递规则,如果想修改state中的数据只能通过提交一个mutation
    2. 控制台报错:计算属性没有setter

    官方提供了两种解决方法,我更倾向于下面这种,给计算属性添加setter,并在setter中提交mutation修改state:

    <template>
      <input v-model="message">
    </template>
    <script>
       export default {
        name: 'app',
        data () {
          return {}
        },
        computed: {
          message: {
            get () {
              return this.$store.state.test.message
            },
            set (value) {
              this.$store.commit('updateMessage', value)
            }
          }
        }
        methods: {}
      }
    </script>
    

    Vuex持久化

    推荐插件vuex-persist

    安装插件:
    npm install --save vuex-persist

    引入、配置、加载插件:
    src/store/persist.js

    import VuexPersistence from 'vuex-persist'
    
    const persist = new VuexPersistence({
      // 其他参数看文档
      storage: window.sessionStorage
    })
    export default persist.plugin
    

    src/store/index.js

    import ...
    import persist from './persist'
    
    Vue.use(Vuex)
    
    export default new Vuex.Store({
      modules: {
        user,
        product,
        windowInfo
      },
      plugins: [persist]
    })
    
    

    现在刷新浏览器数据也不会重置了!

    总结

    以上就是vuex比较常规的操作了,第一次看官方文档的我是懵逼的、无助的,但是用了一段时间vuex再重新看文档的时候会有很多收获。
    希望这篇文章对你有帮助~

    相关文章

      网友评论

        本文标题:Vuex模块化应用实践

        本文链接:https://www.haomeiwen.com/subject/srdkuqtx.html