最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Vue基础知识巩固之全面了解Vuex,比官方更易懂(下)

    正文概述 掘金(十里青山)   2021-03-27   506

    Vuex进阶操作

    辅助函数

    mapState

    前面我们说了,在组件用访问store实例中的值时我们可以使用computed计算属性,如果我们访问某一个值还好,但是如果我们需要访问多个值时,就需要在computed中写多个计算属性,这样既不省事也不优雅,对于这样的情况,Vuex为我们准备了辅助函数。

    // 在单独构建的版本中辅助函数为 Vuex.mapState
    import { mapState } from 'vuex'
    
    export default {
      // ...
      computed: mapState({
        // 箭头函数可使代码更简练
        count: state => state.count,
    
        // 传字符串参数 'count' 等同于 `state => state.count`
        countAlias: 'count',
    
        // 为了能够使用 `this` 获取局部状态,必须使用常规函数
        countPlusLocalState (state) {
          return state.count + this.localCount
        }
      })
    }
    

    当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。

    computed: mapState([
      // 映射 this.count 为 store.state.count
      'count'
    ])
    

    mapState 函数返回的是一个对象。我们如何将它与局部计算属性混合使用呢?通常,我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 computed 属性。但是自从有了对象展开运算符,我们可以极大地简化写法:

    computed: {
      localComputed () { /* ... */ },
      // 使用对象展开运算符将此对象混入到外部对象中
      ...mapState({
        // ...
      })
    }
    

    mapGetters

    mapGetters 也放在 computed 中,使用方法和mapState差不多

    import { mapGetters } from 'vuex'
    
    export default {
      // ...
      computed: {
      // 使用对象展开运算符将 getter 混入 computed 对象中
        ...mapGetters([
          'doneTodosCount',
          'anotherGetter',
          // ...
        ])
      }
    }
    

    如果你想将一个 getter 属性另取一个名字,使用对象形式:

    ...mapGetters({
      // 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
      doneCount: 'doneTodosCount'
    })
    

    mapMutations

    import { mapMutations } from 'vuex'
    
    export default {
      // ...
      methods: {
        ...mapMutations([
          'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
    
          // `mapMutations` 也支持载荷:
          'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
        ]),
        ...mapMutations({
          add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
        })
      }
    }
    

    mapActions

    export default {
      // ...
      methods: {
        ...mapActions([
          'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
    
          // `mapActions` 也支持载荷:
          'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
        ]),
        ...mapActions({
          add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
        })
      }
    }
    

    modules

    在写Vue项目时,如果一个项目过于庞大,我们会对项目进行拆分成一个个单独的组件,Vuex也是如此,当一个store实例中存储了过多内容的时候,它将变得非常臃肿,此时,我们也可以将它拆分成一个个单独的组件,类似于下面这样。

    import Vue from 'vue'
    import Vuex from 'vuex'
    
    Vue.use(Vuex)
    
    const moduleA = {
      state: () => ({ count: 5 }),
      mutations: {
        increment (state) {
          state.count++
        }
      },
      actions: { },
      getters: {
        name: () => { return 'moduleA' }
      }
    }
    const moduleB = {
      state: () => ({ count: 10 }),
      mutations: {
        increment (state) {
          state.count++
        }
      },
      actions: { },
      getters: {
        name: () => { return 'moduleB' }
      }
    }
    
    export default new Vuex.Store({
      state: {
      },
      mutations: {
      },
      actions: {
      },
      modules: {
        a: moduleA,
        b: moduleB
      }
    })
    
    
    

    模块的局部状态

    对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象。

    同样,对于模块内部的 action,访问模块内部的state可以使用 context.state ,访问根节点的state则可以使用context.rootState:

    对于模块内部的 getter,根节点state会作为第三个参数传递进去(顺序不能错)

    getters: {
        sumWithRootCount (state, getters, rootState) {
          return state.count + rootState.count
        }
      }
    

    那我们如何去访问module中的状态和mutation等呢?在module中,state是module的局部状态,所以我们可以这样访问

    this.$store.state.a.count // -> 5
    

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

      mounted () {
        setInterval(() => {
          this.$store.commit('increment')
        }, 1000)
      }
    

    Vue基础知识巩固之全面了解Vuex,比官方更易懂(下)

    我们触发一个Mutation时,模块内部的同名Mutation会同时被触发,两个模块内的state都发生了改变。action同样会如此,就不演示了,至于getters,同样会被注册到全局命名空间,如果两个module内有同名的getter,则以先引入的module为主。

    import Vue from 'vue'
    import Vuex from 'vuex'
    
    Vue.use(Vuex)
    
    const moduleA = {
      state: () => ({ count: 5 }),
      mutations: {
        increment (state) {
          state.count++
        }
      },
      actions: { },
      getters: {
        name: () => { return 'moduleA' }
      }
    }
    const moduleB = {
      state: () => ({ count: 10 }),
      mutations: {
        increment (state) {
          state.count++
        }
      },
      actions: { },
      getters: {
        name: () => { return 'moduleB' }
      }
    }
    
    export default new Vuex.Store({
      state: {
      },
      mutations: {
      },
      actions: {
      },
      modules: {
        a: moduleA,
        b: moduleB
      }
    })
    
    this.$store.getters.name // -> 'moduleA'
    

    命名空间

    那么,如果我们就想保持每个模块独立,不去影响全局空间,保持更好的封装性怎么办呢?Vuex给我们提供了提供了开启命名空间的选项,我们只需要在模块内部添加 namespaced: true 即可开启模块的命名空间。

    开启了命名空间后,当前模块内的getter 和 action 会收到局部化的 getter,dispatch 和 commit,所以我们的代码无需做任何改变,但是我们在外部也就是vue组件内调用模块内的getters、actions和mutations时则需要加上模块名,由于state本来就是模块内的局部状态,所以加不加命名空间都一样

    const store = new Vuex.Store({
      modules: {
        // 模块名:account
        account: {
          namespaced: true,
    
          // 模块内容(module assets)
          state: () => ({ ... }), // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响 
          getters: {
            isAdmin () { ... } // -> this.$store.getters['account/isAdmin']
          },
          actions: {
            login () { ... } // -> this.$store.dispatch('account/login')
          },
          mutations: {
            login () { ... } // -> this.$store.commit('account/login')
          },
    
          // 嵌套模块
          modules: {
            // 继承父模块的命名空间
            myPage: {
              state: () => ({ ... }),
              getters: {
                profile () { ... } // -> this.$store.getters['account/profile']
              }
            },
    
            // 进一步嵌套命名空间
            posts: {
              namespaced: true,
              state: () => ({ ... }),
              getters: {
                popular () { ... } // -> this.$store.getters['account/posts/popular']
              }
            }
          }
        }
      }
    })
    
    

    那么如果我们开启了命名空间,又想在模块内部访问全局内容怎么办?

    在 getter 中,我们可以接收第三个参数 rootState访问全局的 state 和 第四个参数 rootGetters 访问全局的getter

    // 模块内部
    getters:{
      someGetter (state, getters, rootState, rootGetters) {
        ...
      },
    }
    

    如果我们想要在模块内部的action中调用全局的action或者Mutation,只需要在调用的时候将 { root: true } 作为第三参数传给 dispatch 或 commit 即可。

    // 模块内部
    actions: {
        someAction ({dispatch, commit}) {
            dispatch('someOtherAction', null, { root: true })
            commit('someMutation', null, { root: true })
        }
    }
    
    

    在带命名空间的模块注册全局 action 若需要在带命名空间的模块注册全局 action,你可添加 root: true,并将这个 action 的定义放在函数 handler 中。就像我们需要深度监听时候使用watch一样,例如:

      // 模块内部
      namespaced: true,
      actions: {
        someAction: {
          root: true, // 此action将被注册到全局空间内
          handler (namespacedContext, payload) { ... }
        }
      }
    

    还记得我们上面的辅助函数吗?那如果我们在模块内部开启了命名空间,又该如何去使用辅助函数呢?

    mapGetters 和 mapState用法差不多,mapActions 和 mapMutations 用法差不错,这里就不重复演示了

    我们共有三种方法去使用

    const moduleA = {
      namespaced: true,
      state: () => ({ count: 5 }),
      mutations: {
        addMutation (state) {...}
      },
      actions: {
        addAction ({commit}) {...}
      }
    }
    
    export default new Vuex.Store({
      state: {
        count: 1
      },
      modules: {
        moduleA // moduleA:moduleA 使用ES6的语法简写为 moduleA
      }
    })
    
    
    
    // => 可以简化为
    
    computed: {
      ...mapState('moduleA', {
        count: state => state.count
      })
    }
    
    

    第一种:在用的时候带上路径

    import { mapState, mapMutations } from 'vuex'
    
    computed: {
      ...mapState({
        count: state => state.moduleA.count // => 5
      })
    },
    methods: {
      ...mapMutations([
        'moduleA/addMutation' // 用的时候 this['moduleA/addMutation']()
      ])
      ...mapMutations({
        addMutation: 'moduleA/addMutation' // 用的时候 this.addMutation()
      })
    }
    

    第二种:在第一个参数传入module名

    import { mapState, mapMutations } from 'vuex'
    
    computed: {
      ...mapState(‘moduleA’, {
        count: state => state.count // => 5
      })
    },
    methods: {
      ...mapMutations(‘moduleA’, [
        'addMutation' // 用的时候 this.addMutation()
      ])
      ...mapMutations(‘moduleA’, {
        addMutation: 'addMutation' // 用的时候 this.addMutation()
      })
    }
    

    第三种:使用 createNamespacedHelpers

    import { createNamespacedHelpers } from 'vuex'
    const { mapState, mapMutations } = createNamespacedHelpers('moduleA')
    
    computed: {
      ...mapState({
        count: state => state.count // => 5
      })
    },
    methods: {
      ...mapMutations([
        'addMutation' // 用的时候 this.addMutation()
      ])
      ...mapMutations({
        addMutation: 'addMutation' // 用的时候 this.addMutation()
      })
    }
    
    

    模块重用

    有时我们可能需要创建一个模块的多个实例,例如:

    • 创建多个 store,他们公用同一个模块 (例如当 runInNewContext 选项是 false 或 'once' 时,为了在服务端渲染中避免有状态的单例 )
    • 在一个 store 中多次注册同一个模块

    如果我们使用一个纯对象来声明模块的状态,那么这个状态对象会通过引用被共享,导致状态对象被修改时 store 或模块间数据互相污染的问题。

    实际上这和 Vue 组件内的 data 是同样的问题。因此解决办法也是相同的——使用一个函数来声明模块状态(仅 2.3.0+ 支持):

    const MyReusableModule = {
      state: () => ({
        foo: 'bar'
      }),
      // mutation, action 和 getter 等等...
    }
    

    扩展知识

    v-model双向绑定state中的值

    官方不推荐我们直接使用 v-model 直接去绑定state中的值,并且如果我们开启了严格模式,这样做还会报错,那如果我们用vue的思维去解决这个问题,就是使用v-model绑定一个值,然后去监听这个值的变化,之后使用commit去改变state中的值,这样做难免过于繁琐,官方推荐的最优雅的方式是去利用计算属性的 gettersetter 属性

    // ...
    mutations: {
      updateMessage (state, message) {
        state.obj.message = message
      }
    }
    
    <input v-model="message">
    
    // ...
    computed: {
      message: {
        get () {
          return this.$store.state.obj.message
        },
        set (value) {
          this.$store.commit('updateMessage', value)
        }
      }
    }
    

    好啦,本篇文章的内容就到此为止啦,时间仓促,知识繁多,学艺不精,难免出错,如果各位大佬有人发现文中不对的地方,希望给与指正,谢谢,希望可以帮到大家,祝大家工作顺利?


    起源地下载网 » Vue基础知识巩固之全面了解Vuex,比官方更易懂(下)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元