zoukankan      html  css  js  c++  java
  • Vuex-一个专为 Vue.js 应用程序开发的状态管理模式

    为什么会出现Vuex

    非父子关系的组件如何进行通信?(Event Bus)
    bus.js

    
    import Vue from 'vue';
    export default new Vue();
    

    foo.vue

    
    import bus from './bus.js';
    export default {
        methods: {
            changeBroData() {
                bus.$emit('changeBarData');
            }
        }
    }
    

    bar.vue

    
    import bus from './bus.js';
    export default {
        created() {
            bus.$on('changeBarData',() => {
                this.count++;
            });
        }
    }
    

    查看效果

    但是当我们需要修改这个操作的时候,我们需要动3个地方,倘若项目小的话还倒好说,但是对于大项目组件间交互很多的概况,Event Bus就会表现的很吃力。Vuex的出现就是为了解决这一状况。

    Vuex介绍

    安装:
    script标签引入
    https://unpkg.com/vuex
    NPM
    npm install vuex --save-dev

    
    import Vue from 'vue';
    import Vuex from 'vuex';
    Vue.use(Vuex);
    

    每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:

    1. Vuex的状态存储是响应式的。当Vuex的状态属性发生变化时,相应的组件也会更新。
    2. 无法直接修改Vuex的状态。只能通过显式的提交(commit)。

    简单的Store

    
    const store = new Vuex.Store({
      state: {
        count: 0
      },
      mutations: {
        increase(state) {
          state.count++;
        }
      }
    });
    store.commit('increase');
    console.log(store.state.count); // 1
    

    State

    从上文我们知道Vuex是响应式的,我们如何在Vue实例中使用Vuex中的实例呢,自然离不开计算属性computed了。

    在 Vue 组件中获得 Vuex 状态

    
    //仓库
    const store = new Vuex.Store({
      state: {
        count: 1
      }
    });
    //foo组件
    const foo = {
      template: `
                <div>
                    {{ count }}
                <div>
                `,
      computed: {
        count: () => store.state.count
      }
    };
    

    这时候问题就来了,如果这样进行引入store的话,组件就会以来全局状态单例。
    Vuex 通过 store 选项,提供了一种机制将状态从根组件“注入”到每一个子组件中(需调用 Vue.use(Vuex)):

    
    //挂载App
    new Vue({
      el: '#app',
      //注入store选项
      store,
      render: h => h(App)
    });
    //foo组件
    const foo = {
      template: `
                <div>
                    {{ count }}
                <div>
                `,
      computed: {
        count() {
          // 不能使用箭头函数,不然this就不是Vue实例了
          // 通过this.$store获取到store实例
          return this.$store.state.count
        }
      }
    };
    

    如果有很多状态需要映射,我们岂不是要写好多代码,这时候需要用到mapState辅助函数,使用 mapState 辅助函数帮助我们生成计算属性。
    辅助函数 实例1

    
    const foo = {
      template: `
                <div>
                    count: {{ count }}
                    countAlias: {{ countAlias }}
                    countPlusLocalCount: {{ countPlusLocalCount }}
                <div>
                `,
      data() {
        return {
          localCount: 2
        };
      },
      computed: Vuex.mapState({
        //只处理仓库中的count
          count: state => state.count*2,
        //映射
        countAlias: 'count',
        //需要用到Vue实例的话不能使用箭头函数不然this无法获取到Vue实例
        countPlusLocalCount(state) {
          return state.count + this.localCount;
        }
      })
    };
    

    当然当映射名与state中属性名相同时,可以通过mapState(['count'])这种形式书写。
    因为组件本身也有许多计算属性直接使用mapState的话无法扩充computed了,这时候我们可以使用ES新属性: 对象展开运算符

    
    computed: {
        localComputed() {
            return this.count;
        },
        ...Vuex.mapState({
            //只处理仓库中的count
                  count: state => state.count*2,
            //映射
            countAlias: 'count',
            //需要用到Vue实例的话不能使用箭头函数不然this无法获取到Vue实例
            countPlusLocalCount(state) {
              return state.count + this.localCount;
            }
        })
    }
    

    ATT:使用 Vuex 并不意味着你需要将所有的状态放入 Vuex。虽然将所有的状态放到 Vuex 会使状态变化更显式和易调试,但也会使代码变得冗长和不直观。如果有些状态严格属于单个组件,最好还是作为组件的局部状态。你应该根据你的应用开发需要进行权衡和确定。

    Getter

    有时候我们需要过滤/处理state中的属性值,就像Vue实例中我们需要处理data数据一样,Vue有computed。Vuex同样提供给我们一个属性getter,getter就是Vuex的“计算属性”。
    Getter接收state作为第一个参数

    
    //仓库
    const store = new Vuex.Store({
      state: {
        users: [{
          name: 'jason',
          id: 1,
          female: false
        }, {
          name: 'molly',
          id: 2,
          female: true
        }, {
          name: 'steven',
          id: 3,
          female: false
        }]
      },
      getters: {
        // 过滤所有属性中female是true的对象
        getFemaleUsers: state => state.users.filter(user => user.female)
      }
    });
    console.log(store.getters.getFemaleUsers);
    //[{ "name": "molly", "id": 2, "female": true }]
    

    当然Getter同样有辅助函数 mapGetters将 store 中的 getter 映射到局部计算属性
    直接上对象展开运算符了
    辅助函数 实例1

    
    //foo组件
    const foo = {
      template: `
                <div>
                    femaleList: {{ femaleList }}
                <div>
                `,
      computed: {
        // 属性名相同的不再阐述
          ...Vuex.mapGetters({
          femaleList: 'getFemaleUsers'
        })
      }
    };
    

    Mutation

    更改 Vuex 的 store 中的状态的唯一方法是提交 mutation
    提交时额外的参数被称为载荷
    普通风格提交方式

    
    store.commit('mutationName', {
        params: '参数'
    });
    

    对象风格提交方式

    
    store.commit({
        type: 'mutationName',
        params: '参数'
    });
    

    注意点:

    1. 设置对象新属性时
      方法1:Vue.set(obj, 'newVal', 100)
      方法2:obj = {...obj, newVal: 100};
    2. Mutations的事件类型尽量使用常量,并用一个文件进行维护。方便协同开发以及维护。

      
      // 存储事件名mutations-types.js
      export const MUTATIONS_GETDATA = 'MUTATIONS_GETDATA';
      export const MUTATIONS_SUCCESS = 'MUTATIONS_SUCCESS';
      
      
      import { MUTATIONS_GETDATA ,MUTATIONS_SUCCESS } from 'path/mutations-types.js';
      
      const store = new Vuex.Store({
          mutations: {
              [MUTATIONS_GETDATA]() {
                  // todo
              },
              [MUTATIONS_SUCCESS]() {
                  // todo
              }
          }
      });
      
      
    3. Mutations中的函数必须都是同步函数,异步函数都要写在Actions中。

    在组件中提交Mutation
    $store.commit('xxx','yyy')
    当然也有对应的辅助函数帮助我们映射到对应的methods方法中

    
    import { mapMutations } from 'vuex';
    export default {
        methods: {
            ...mapMutations([
                'event1',
                'event2'
            ]),
            ...mapMutations({
                eventAlias: 'event3' //将this.eventAlias()映射为this.$store.commit('event3')
            })
        }
    };
    

    Action

    Action与Mutation的不同在于Action不直接修改状态只是做commit一个mutation、然后就是可以做异步操作。
    简单的Action

    
    const INCREASE_COUNT = 'INCREASE_COUNT';
    const store = new Vuex.Store({
      state: {
        count: 0
      },
        mutations: {
        [INCREASE_COUNT](state) {
          state.count++;
        }
      },
      actions: {
        add({ commit }) {
          commit(INCREASE_COUNT);
        }
      }
    });
    

    看到这我们或许以为Actions的作用与Mutations不一样么,对到现在为止作用是一样的,但是Actions可以执行异步操作

    
    const INCREASE_COUNT = 'INCREASE_COUNT';
    const store = new Vuex.Store({
      state: {
        count: 0
      },
        mutations: {
        [INCREASE_COUNT](state) {
          state.count++;
        }
      },
      actions: {
        add({ commit }) {
            setTimeout(() => {
                commit(INCREASE_COUNT);
            }, 1000);
        }
      }
    });
    

    Action的分发也有两种方式
    普通风格分发方式

    
    store.dispatch('actionName', {
        params: '参数'
    });
    

    对象风格分发方式

    
    store.dispatch({
        type: 'actionName',
        params: '参数'
    });
    

    辅助函数mapActions用法与mapMutations无异
    代码链接

    
    methods: {
        ...mapActions({
            add: 'add'
        })
    }
    

    组合Action

    
    actions: {
      actionA ({ commit }) {
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            commit('someMutation')
            resolve()
          }, 1000)
        })
      },
      // ...
      actionB ({ dispatch, commit }) {
        return dispatch('actionA').then(() => {
          commit('someOtherMutation')
        })
      }
    }
    store.dispatch('actionA').then(() => {
      // ...
    })
    或者
    store.dispatch('actionB');
    

    Module

    如果项目庞大的话我们需要维护很多状态,这时候store会变得非常庞大,我们就需要store分割成很多模块(Module),每个模块同样拥有自己的state,getters,mutations,actions以及modules

    
    const moduleA = {
      state: {
        a: 'A'
      }
    };
    const moduleB = {
      state: {
        a: 'B'
      }
    };
    const store = new Vuex.Store({
      modules: {
        moduleA,
        moduleB
      }
    });
    console.log(store.state.moduleA.a); //A
    console.log(store.state.moduleB.a); //B
    
    1. 模块内部的mutationgetter接收的第一个参数都是局部状态对象

      
      例如
      const moduleA = {
          state: {
              price: 50,
              count: 2
          },
          getters: {
              totalPrice: state => state.price*state.count
          },
          mutations: {
              decreaseCount(state) {
                  state.count--;
              }
          }
      };
      
    2. 模块内部的getter,根节点的状态会在第三个参数展示出来。
      注:根节点的状态就是指的store.state

      
      const moduleB = {
        state: {
            count: 2
        },
        getters: {
          sum: (state, getters, rootState) => state.count+rootState.count
        }
      };
      const store = new Vuex.Store({
        state: {
          count: 1
        },
        modules: {
          moduleB
        }
      });
      console.log(store.getters.sum);// 3
      
    3. 模块内部的action,局部状态:context.state根部状态:context.rootState

      
      const moduleC = {
        state: {
            count: 2
        },
        mutations: {
          increaseCount(state) {
              state.count++;
          }
        },
        actions: {
          addCount({ commit, state, rootState }) {
              let sum = state.count + rootState.count;
              if(sum % 3 === 0) {
                  commit('increaseCount');
              }
          }
        }
      };
      const store = new Vuex.Store({
        state: {
          count: 1
        },
        modules: {
          moduleC
        }
      });
      console.log(store.state.moduleC.count);// 2
      store.dispatch('addCount');
      console.log(store.state.moduleC.count);// 3
      

    命名空间
    默认情况下,模块内部的 actionmutationgetter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutationaction 作出响应。
    如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为命名空间模块。当模块被注册后,它的所有 getteractionmutation 都会自动根据模块注册的路径调整命名。(摘自官网)
    注1:特别提出:模块内的state是嵌套的,namespaced属性不会对其产生丝毫影响。
    注2:没加namespaced: true的模块会继承父模块的命名空间
    代码链接

    
    const store = new Vuex.Store({
        modules: {
        moduleA: {
          namespaced: true,
          getters: {
            count: () => 0 // store.getters['moduleA/count']
          },
          modules: {
            moduleB: {
              getters: {
                  count1: () => 1 //继承了父模块的命名空间 store.getters['moduleA/count1']
                }
            },
            moduleC: {
              namespaced: true,
              getters: {
                  count2: () => 2 //继承了父模块的命名空间 store.getters['moduleA/moduleC/count1']
                }
            }
          }
        }
      }
    });
    console.log(store.getters['moduleA/count']);// 0
    console.log(store.getters['moduleA/count1']);// 1
    console.log(store.getters['moduleA/moduleC/count2']);// 2
    

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

    模块内部希望使用全局state与全局getter

    1. rootStaterootGetters会作为getter第三个第四个参数传入

      
      someGetter: (state, getters, rootState, rootGetters) => {
          //todo
      }
      
      
      const store = new Vuex.Store({
        getters: {
          someOtherGetter: state => 'ROOT'
        },
        modules: {
          moduleA: {
            namespaced: true,
            getters: {
              someGetter(state,getters,rootState,rootGetters) {
                return getters.someOtherGetter; // INSIDE
                // return rootGetters.someOtherGetter; // ROOT
              },
              someOtherGetter: state => "INSIDE"
            }
          }
        }
      });
      console.log(store.getters['moduleA/someGetter']);
      
    2. 也会通过context.rootStatecontext.rootGetts传入

      
      someAction:(context) => {
          //todo
      }
      或者
      someAction:({ commit, dispatch, rootState, rootGetters }) => {
          //todo
      }
      

    模块内部希望使用全局mutation与全局action,只需要在执行分发或者提交的时候在第三个参数位置传入{ root: true }

    
    dispatch('someOtherAction', null, {root: true});
    commit('someOtherMutation', null, {root: true});
    

    栗子(没有写mutation与state的可以自行尝试)

    
    const store = new Vuex.Store({
      getters: {
        someOtherGetter: state => 'ROOT'
      },
      actions: {
        someOtherAction() {
          console.log('ROOT_ACTION');
        }
      },
      modules: {
        moduleA: {
          namespaced: true,
          getters: {
            someGetter(state,getters,rootState,rootGetters) {
              return getters.someOtherGetter; // INSIDE
              // return rootGetters.someOtherGetter; // ROOT
            },
            someOtherGetter: state => "INSIDE"
          },
          actions: {
            someAction({ dispatch, getters, rootGetters }) {
              console.log(getters.someOtherGetter);//INSIDE
              console.log(rootGetters.someOtherGetter);//ROOT
              dispatch('someOtherAction');//INSIDE_ACTION
              dispatch('someOtherAction', null, {root: true});//ROOT_ACTION
            },
            someOtherAction() {
              console.log('INSIDE_ACTION');
            }
          }
        }
      }
    });
    console.log(store.getters['moduleA/someGetter']);
    store.dispatch('moduleA/someAction');
    

    当我们将store里的状态映射到组件的时候,会出现下面的问题

    
    computed: {
        ...mapState({
            b1:state => state.moduleA.moduleB.b1,
            b2:state => state.moduleA.moduleB.b2
        })
    },
    methods: {
        ...mapActions({
            'moduleA/moduleB/action1',
            'moduleA/moduleB/action2'
        })
    }
    是不是比较繁琐,代码太过冗余。
    

    当然mapStatemapGettersmapMutationsmapActions这些辅助函数都可以将空间名称字符串作为第一个参数传递,这样上面的例子可以简化成

    
    computed: {
        ...mapState('moduleA/moduleB', {
            b1:state => state.b1,
            b2:state => state.b2
        })
    },
    methods: {
        ...mapActions('moduleA/moduleB', {
            'action1',
            'action2'
        })
    }
    

    当然还有一个方法,使用Vuex提供的createNamespacedHelpers创建基于某个命名空间函数,它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数。

    
    import { createNamespacedHelpers } from 'vuex';
    const { mapState , mapActions } = createNamespacedHelpers('moduleA/moduleB');
    export default {
        computed: {
            ...mapState({
                b1:state => state.b1,
                b2:state => state.b2
            })
        },
        methods: {
            ...mapActions({
                'action1',
                'action2'
            })
        }
    }
    

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

    1. 创建多个 store,他们公用同一个模块
    2. 在一个 store 中多次注册同一个模块

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

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

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

    原文地址:https://segmentfault.com/a/1190000012645384

  • 相关阅读:
    flash 显示对象的getRect()方法的问题
    John Maccarthy
    一个c++ 2d图形引擎 AGG
    lisp 编程入门
    linux图形开发工具
    wxDevC++ – Dev C++的愛好者一定要知道的C++ IDE
    备忘 html5 canvas context2d/3d
    windows下使用boost库可以下载编译好的二进制安装包
    酷壳
    [.NET] : Provider Pattern
  • 原文地址:https://www.cnblogs.com/lalalagq/p/9960288.html
Copyright © 2011-2022 走看看