• Vuex的五个核心概念—state/getter/mutation/action/module


    1. State

    Vuex 使用单一状态树——用一个对象包含全部的应用层级状态。至此它就是“唯一数据源 (SSOT)”。这也意味着,每个应用将仅仅包含一个 store 实例。

    组件中获得 Vuex 状态

    • 计算属性

      import store from './store'
      const Counter = {
        template: `<div>{{ count }}</div>`,
        computed: {
          count () {
            return store.state.count
          }
        }
      }
      

      然而,组件可能需要频繁导入store

    • store选项

      //父组件
      import store from './store'
      import Counter from './Counter'
      new Vue({
        el: '#app',
        store,	//将store从根组件“注入”到每一个子组件中
        render:c=>c(Counter),
      })
      
      //子组件Counter.vue能通过 `this.$store` 访问:
      <template>
      <div>{{count}}</div>
      </template>
      
      <script>
      export default {
          computed: {
          count () {
            return this.$store.state.count
          }
        }
      }
      </script>
      

    辅助函数mapState

    当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性

    //store.js
    import Vue from 'vue';
    import Vuex from 'vuex';
    Vue.use(Vuex)
    
    export default new Vuex.Store({
        state:{
            count:4
        }
    })
    
    //Counter.vue
    
    <template>
        <div>{{sumCount}}</div>
    </template>
    
    <script>
    import store from './store'
    import {mapState} from 'vuex' //引入辅助函数
    export default {
      data(){
        return{
          localCount:7
        }
      },
      computed:mapState({
            count:state=>state.count, //返回count,等同于count () {return this.$store.state.count}
            countAlias:'count', //count的别名是countAlias,这时候countAlias等同于count(注意单引号)
    
            sumCount(state){ //处理多状态:store的数据加上本地的数据
              return state.count+this.localCount
            }
      })
    
    }
    </script>
    

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

    computed: mapState([
      'count'  //等同于count:state=>state.count,
    ])
    

    2. Getter

    前言

    假设我们需要处理store中state,例如对列表进行过滤并计数

    computed: {
      doneTodosCount () {
        return this.$store.state.todos.filter(todo => todo.done).length
      }
    }
    

    然而如果很多组件都需要用到这个计算属性,那么我们只能复制这个函数,或者频繁导入它。

    Vuex 允许我们在 store 中定义“getter”,就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

    应用

    1. getter的参数

    • getter接受state作为参数

      const store = new Vuex.Store({
        state: {
          todos: [
            { id: 1, text: '...', done: true },
            { id: 2, text: '...', done: false }
          ]
        },
          
        getters: {
          doneTodos: state => {
            return state.todos.filter(todo => todo.done)
          }
        }
      })
      
    • getter接受第二个参数getter

      getters: {
        doneTodos: state => {
            return state.todos.filter(todo => todo.done)
        }
        doneTodosCount: (state, getters) => {
          return getters.doneTodos.length //通过getter访问到doneTodos属性
        }
      }
      

    2. 通过属性访问

    //访问
    store.getters.doneTodos 
    store.getters.doneTodosCount 
    
    //组件中使用
    computed: {
      doneTodosCount () {
        return this.$store.getters.doneTodosCount
      }
    }
    

    3. 通过方法访问

    让 getter 返回一个函数,来实现给 getter 传参。这对在 store 里的数组进行查询时非常有用

    getters: {
      // ...
      getTodoById: (state) => (id) => {
        return state.todos.find(todo => todo.id === id)
      }
    }
    
    //访问
    store.getters.getTodoById(2) //返回元素id与传过去的id相等的元素
    

    辅助函数mapGetters

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

    3. Mutation

    简单示例

    更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。 mutation 类似于事件:每个 mutation 都有一个 事件类型 (type) 和 一个 回调函数 (handler)。它接受 state 作为第一个参数:

    const store = new Vuex.Store({
      state: {
        count: 1
      },
        
      mutations: {
        //事件类型:increment;回调函数为后面部分;
        increment (state) {
          state.count++
        }
      }
    })
    

    想要调用该事件类型的回调,需要使用store.commit(),并传入字符串的事件类型

    store.commit('increment')
    

    提交载荷(Payload)

    可以向 store.commit 传入额外的参数,这些额外参数就是 mutation 的 载荷(payload)

    mutations: {
      increment (state, n) {
        state.count += n
      }
    }
    
    store.commit('increment', 10)
    

    在大多数情况下,载荷应该是一个对象,这样更易读:

    mutations: {
      increment (state, payload) {
        state.count += payload.amount
      }
    }
    
    store.commit('increment', {
      amount: 10
    })
    

    提交方式

    1. 没有载荷

    store.commit('事件类型')
    

    2. 有载荷:

    //一般写法
    store.commit('increment', 10)
    
    //载荷为一个对象时
    store.commit('事件类型', {
      amount: 10
    })
    
    //载荷为一个对象时-对象风格写法
    store.commit({
      type: '事件类型',
      amount: 10
    })
    

    3. 组件中提交

    this.$store.commit('事件类型')
    

    mapMutations辅助函数

    methods:{
        //...
        ...mapMutations([
          'increment',  //相当于this.$store.commit('increment')
          'incrementBy', //相当于`this.$store.commit('incrementBy', n)`
          'incrementBy2' //相当于this.$store.commit('incrementBy2',{amount})
        ]),
        ...mapMutations({
          add:'increment' //此时this.add相当于$store.commit('increment')
        })
      }
    

    注意:Mutation 必须是同步函数

    使用常量代替Mutation类型

    // mutation-types.js
    export const SOME_MUTATION = 'SOME_MUTATION'
    
    // store.js
    import Vuex from 'vuex'
    import { SOME_MUTATION } from './mutation-types'
    
    const store = new Vuex.Store({
      state: { ... },
      mutations: {
        // 可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
        [SOME_MUTATION] (state) {
          // mutate state
        }
      }
    })
    

    4. Action

    前言

    Action 类似于 mutation,不同在于:

    • Action 提交的是 mutation,而不是直接变更状态。
    • Action 可以包含任意异步操作

    简单示例

    //一个往数组添加随机数的实例:
    const store = new Vuex.Store({
      state: {
        msg: []
      },
      mutations: {
        addMessage(state,msg){
          state.msg.push(msg)
        }
      },
      
    //一个异步操作:
    actions: {
         getMessages(context){
           fetch('https://www.easy-mock.com/mock/5f4a32907e1a7f3146e313e7/api/getnum')
             .then(res=>res.json())
             .then(data=>{
               context.commit('addMessage',data.data.number)
             })       
         }
     }
    })
    

    Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以在这个对象中获取 state 和 getters或提交一个 mutation。

    ES6中可以通过参数解构简化代码

    actions: {
         getMessages({commit}){
           //...
               commit('addMessage',data.data.number)
           //...      
         }
     }
    

    分发Action

    1. 普通分发

    Action 通过 store.dispatch 方法触发,

    store.dispatch('getMessages')
    

    2. 载荷分发

    // 普通
    store.dispatch('事件类型', {
      amount: 1
    })
    
    // 对象形式
    store.dispatch({
      type: '事件类型',
      amount: 1
    })
    

    3. 组件中分发

    this.$store.dispatch('事件类型')
    

    mapMutations辅助函数

    import { mapActions } from 'vuex'
    
    export default {
      // ...
      methods: {
        ...mapActions([
          'getMessages', // 相当于`this.$store.dispatch('getMessages')`
        ]),
        ...mapActions({
          addmsg: 'getMessages' //this.addmsg等同于`this.$store.dispatch('getMessages')`
        })
      }
    }
    

    组合Action

    如何知道 action 什么时候结束呢?如何才能组合多个 action,以处理更加复杂的异步流程呢?首先要知道:

    • store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise
    • store.dispatch 仍旧返回 Promise:
    actions: {
      actionA ({ commit }) {
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            commit('someMutation')
            resolve()
          }, 1000)
        })
      }
    }
    

    现在可以指定回调:

    store.dispatch('actionA').then(() => {
      // ...
    })
    

    也可以 另一个action指定回调:

    actions: {
      // ...
      actionB ({ dispatch, commit }) {
        return dispatch('actionA').then(() => {
          commit('someOtherMutation')
        })
      }
    }
    

    最后利用 async / await,我们可以如下组合 action:

    actions: {
      async actionA ({ commit }) {
        commit('gotData', await getData())
      },
      async actionB ({ dispatch, commit }) {
        await dispatch('actionA') // 等待 actionA 完成
        commit('gotOtherData', await getOtherData())
      }
    }
    

    5. Module

    前言

    当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决这个问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块:

    const moduleA = {
      state: () => ({ ... }),
      mutations: { ... },
      actions: { ... },
      getters: { ... }
    }
    
    const moduleB = {
      state: () => ({ ... }),
      mutations: { ... },
      actions: { ... }
    }
    
    const store = new Vuex.Store({
      modules: {
        a: moduleA,
        b: moduleB
      }
    })
    
    store.state.a // -> moduleA 的状态
    store.state.b // -> moduleB 的状态
    

    模块的局部状态

    const moduleA = {
      state: () => ({
        count: 0
      }),
        
      //1. 对于mutation,接收的第一个参数是模块的局部状态对象。
      mutations: {
        increment (state) {
          state.count++
        }
      },
      //2. 对于getter,第一个参数暴露局部状态;第三个参数`rootState`暴露根节点状态
      getters: {
        sumWithRootCount (state, getters, rootState) {
          return state.count + rootState.count
        }
      }
        
      //3. 对于action,局部状态通过 context.state 暴露出来,根节点通过 context.rootState:
      actions: {
        incrementIfOddOnRootSum ({ state, commit, rootState }) {
          if ((state.count + rootState.count) % 2 === 1) {
            commit('increment')
          }
        }
      }
    }
    

    命名空间

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

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

    const store = new Vuex.Store({
      modules: {
        account: {
          namespaced: true,
          state: () => ({ ... }), 
          getters: {isAdmin () { ... }},// 通过 store.getters['account/isAdmin']访问
          actions: {login () { ... }},// 通过 store.dispatch('account/login')分发
          mutations: {login () { ... }},//通过store.commit('account/login')提交
    
          //模块中嵌套模块-继承父模块的命名空间
          modules: {
            //模块1-myPage
            myPage: {
              state: () => ({ ... }),
              getters: {profile () { ... }}//store.getters['account/profile']
            },
    
            //模块2-mposts-进一步嵌套命名空间
            posts: {
              namespaced: true,
              state: () => ({ ... }),
              getters: {popular () { ... }}//store.getters['account/posts/popular']
            }
          }
        }
      }
    })
    

    在带命名空间的模块内访问全局内容

    rootStaterootGetters 会作为第三和第四参数传入 getter,也会通过 context 对象的属性传入 action。

    若需要在全局命名空间内分发 action 或提交 mutation,将 { root: true } 作为第三参数传给 dispatchcommit 即可。

    modules: {
      foo: {
        namespaced: true,
        getters: {
          someOtherGetter: state => { ... }
          //1. 对于getter,使用第四个参数 `rootGetters`访问根getter
          someGetter (state, getters, rootState, rootGetters) {
            getters.someOtherGetter // -> 局部的
            rootGetters.someOtherGetter // -> 全局的
          },
        },
    
        actions: {
          someOtherAction (ctx, payload) { ... }
    	  //2. 对于actions,接受 root 属性来访问根 dispatch 或 commit
          someAction ({ dispatch, commit, getters, rootGetters }) {
            dispatch('someOtherAction') // -> 局部的分发
            dispatch('someOtherAction', null, { root: true }) // -> 全局的分发
    
            commit('someMutation') // -> 局部的提交
            commit('someMutation', null, { root: true }) // -> 全局的提交
          },
        }
      }
    }
    

    在带命名空间的模块注册全局 action

    在action中添加 root: true,并将这个 action 的定义放在函数 handler 中。例如:

    {
      //...
      modules: {
        foo: {
          namespaced: true,
          actions: {
            someAction: {
              root: true,
              handler (namespacedContext, payload) { ... } // -> 定义someAction action
            }
          }
        }
      }
    }
    

    带命名空间的绑定函数

    当使用 mapState, mapGetters, mapActionsmapMutations 这些函数来绑定带命名空间的模块时:

    computed: {
      ...mapState({
        a: state => state.some.nested.module.a,
        b: state => state.some.nested.module.b
      })
    },
    methods: {
      ...mapActions([
        'some/nested/module/foo', // -> this['some/nested/module/foo']()
        'some/nested/module/bar' // -> this['some/nested/module/bar']()
      ])
    }
    

    你可以将模块的空间名称字符串作为第一个参数传递给上述函数,这样所有绑定都会自动将该模块作为上下文,简化后:

    computed: {
      ...mapState('some/nested/module', {
        a: state => state.a,
        b: state => state.b
      })
    },
    methods: {
      ...mapActions('some/nested/module', [
        'foo', // -> this.foo()
        'bar' // -> this.bar()
      ])
    }
    

    简化的另一个方法:使用 createNamespacedHelpers 创建基于某个命名空间辅助函数。它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数:

    import { createNamespacedHelpers } from 'vuex'
    const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')//基于某个命名
    
    export default {
      computed: {
        ...mapState({
          a: state => state.a,
          b: state => state.b
        })
      },
      methods: {
        ...mapActions([
          'foo',
          'bar'
        ])
      }
    }
    

    实例源码

  • 相关阅读:
    博客园的博客
    JAVASCRIPT拷贝内容到剪切板
    adobe acrobat pro 8.0/8.1 激活
    lighttpd+PHP上传文件
    windows上UPNP/DLNA服务器软件测试
    maven环境搭建
    Chrome调试博客地址
    struts2技术内幕读书笔记1
    git推送
    第九周助教小结
  • 原文地址:https://www.cnblogs.com/sanhuamao/p/13595960.html
Copyright © 2020-2023  润新知