美文网首页
Vuex 状态管理模式

Vuex 状态管理模式

作者: 哭泣哭泣帕拉达 | 来源:发表于2020-08-04 22:57 被阅读0次

    Vuex 状态管理模式

    在使用vue开发过程中,经常会遇到一个状态在多个组件之间使用,这时候就需要用vuex来状态管理模式来集中管理解决跨组件通信问题,跨组件通信一般是指非父子组件间 的通信。

    一、核心概念 在这里插入图片描述

    • store:vuex使用一个仓库store对象管理应用的状态和操作过程,一个 store 包括 state, getter, mutation, action 四个属性。vuex里的数据都是响应式的,任何组件使用同意store的数据时,只要store的数据变化,对应的组件也会实时更新。
    • state:状态,vuex的数据源。
    • getter:getter就是对状态进行处理的提取出来的公共部分,对state进行过滤输出。
    • mutation:提交mutation是更改vuex 的store中的状态的唯一方法,并且只能是同步操作。
    • action:对state的异步操作,并通过在action提交mutation变更状态。
    • module:当store对象过于庞大时,可以分成多个module,每个module也会有state, getter, mutation, action 四个属性。

    二、安装

    在使用vue-cli脚手架生成项目使用选择vuex,也可以在使用以下命令安装

    npm install vuex --save
    

    结构如下:

    src/store/index.js

    import Vue from 'vue'
    import Vuex from 'vuex'
    
    Vue.use(Vuex)
    
    export default new Vuex.Store({
      state: {
      },
      mutations: {
      },
      actions: {
      },
      modules: {
      }
    })
    

    main.js

    通过在main.js注册 store,该 store 实例会注入到根组件下的所有子组件中,且子组件能通过 this.$store 访问到。

    import Vue from 'vue'
    import App from './App.vue'
    import router from './router'
    import store from './store'
    
    Vue.config.productionTip = false
    
    new Vue({
      router,
      store,
      render: h => h(App)
    }).$mount('#app')
    

    三、State

    state保存状态,先在state中定义一个状态count

    import Vue from 'vue'
    import Vuex from 'vuex'
    Vue.use(Vuex)
    export default new Vuex.Store({
      state: {
        count: 0
      },
      mutations: {},
      actions: {},
      modules: {}
    }
    

    现在有一个集中管理的状态count,其他组件可以通过计算属性来返回此状态,由于在main.js注册过store,所以可以使用this.$store就可以获取到state。

    <template>
      <div>
        <p>{{count}}</p>
      </div>
    </template>
    
    <script>
      export default {
        computed: {
          count() {
            return this.$store.state.count;
          }
        }
     }
    </script>
    

    mapState 辅助函数

    当一个组件需要多个状态时,为这些状态都做计算属性会很冗余麻烦,可以使用 mapState 辅助函数来帮助生成计算属性。

    <template>
      <div>
        <p>{{count}}</p>
        <p>{{calculation}}</p>
      </div>
    </template>
    
    <script>
      import {
        mapState
      } from 'vuex'
      export default {
        data() {
          return {
            msg: "计数:"
          }
        },
        computed: mapState({
    
          // 简写
          // count: state => state.count,
    
          count(state) {
            return state.count
          },
    
    
          // 使用组件内状态+store状态
          calculation(state) {
            return this.msg + state.count
          }
        })
      }
    </script>
    
    

    对象展开运算符

    ...对象展开运算符,...mapState语法糖简化了写法

    <template>
      <div>
        <p>{{count}}</p>
      </div>
    </template>
    
    <script>
      import {
        mapState
      } from 'vuex'
      export default {
        computed: {
          ...mapState(['count'])
        }
      }
    </script>
    

    四、Getter

    getter就是对状态进行帅选过滤操作,处理过后返回给组件使用。

    先定义一组list,然后再对list筛选。

    import Vue from 'vue'
    import Vuex from 'vuex'
    
    Vue.use(Vuex)
    
    export default new Vuex.Store({
      state: {
        list: [{
            name: "鲁班七号",
            game: "王者荣耀"
          },
          {
            name: "亚索",
            game: "英雄联盟"
          }, 
          {
            name: "德玛西亚之力",
            game: "英雄联盟"
          }, {
            name: "亚瑟",
            game: "王者荣耀"
          },
          {
            name: "阿古朵",
            game: "王者荣耀"
          },
          {
            name: "努努",
            game: "英雄联盟"
          }
        ]
      },
      getters: {
        lol: state => {
          return state.list.filter(p => p.game=="英雄联盟");
        }
      },
      mutations: {},
      actions: {},
      modules: {}
    })
    

    getter会暴露出store.getters 对象,可以以属性的方式访问

    <template>
      <div>
        <ul>
          <li v-for="(item, index) in list" :key="index">{{ item.name }}</li>
        </ul>
      </div>
    </template>
    
    <script>
      export default {
        computed: {
          list() {
            return this.$store.getters.lol;
          },
        }
      }
    </script>
    

    mapGetters 辅助函数

    mapGetters辅助函数仅仅是将 store 中的 getter 映射到局部计算属性

    <template>
      <div>
        <ul>
          <li v-for="(item, index) in list" :key="index">{{ item.name }}</li>
        </ul>
      </div>
    </template>
    
    <script>
      import {
        mapGetters
      } from 'vuex'
      export default {
        computed: {
         ...mapGetters({
             list: 'lol'
         })
        }
      }
    </script>
    

    五、Mutation

    如果我们想修改store里的state状态值时,我们不可以直接在组件内去修改,而是通过提交mutation来进行修改,mutation类似于事件。我们来实现一个加减计数。

    import Vue from 'vue'
    import Vuex from 'vuex'
    
    Vue.use(Vuex)
    
    export default new Vuex.Store({
      state: {
       count:0
      },
      getters: {},
      mutations: {
        // 增加
        increase(state){
          state.count++
        },
        // 减少
        decrease(state){
          state.count--
        }
      },
      actions: {},
      modules: {}
    })
    

    在组件内,通过this.$store.commit方法来执行mutation,

    <template>
      <div>
        <input type="button" value="+" @click="increase" />
        <input type="button" value="-" @click="decrease" />
        <p>计数:{{count}}</p>
      </div>
    </template>
    
    <script>
      import {
        mapState
      } from 'vuex'
      export default {
        methods: {
          increase() {
            this.$store.commit('increase');
          },
          decrease() {
            this.$store.commit('decrease');
          }
        },
        computed: {
          ...mapState(['count'])
        }
      }
    
    </script>
    

    Payload 提交载荷

    在提价mutation时可以传入额外的参数,即荷载(payload)

    例如我想count每次改变自定义值

    import Vue from 'vue'
    import Vuex from 'vuex'
    
    Vue.use(Vuex)
    
    export default new Vuex.Store({
      state: {
        count: 0
      },
      getters: {},
      mutations: {
        // 增加
        increase(state, payload) {
          state.count += payload
        },
        // 减少
        decrease(state, payload) {
          state.count -= payload
        }
      },
      actions: {},
      modules: {}
    })
    
    <template>
      <div>
        <input type="button" value="+" @click="increase" />
        <input type="button" value="-" @click="decrease" />
        <p>计数:{{count}}</p>
      </div>
    </template>
    
    <script>
      import {
        mapState
      } from 'vuex'
      export default {
        methods: {
          increase() {
            this.$store.commit('increase',10);  //每次加10
          },
          decrease() {
            this.$store.commit('decrease',5); //每次减5
          }
        },
        computed: {
          ...mapState(['count'])
        }
      }
    </script>
    

    官网建议大多数情况下载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读:

      mutations: {
        // 增加
        increase(state, payload) {
          state.count += payload.customcount
        },
        // 减少
        decrease(state, payload) {
          state.count -= payload.customcount
        }
      },
    
        methods: {
          increase() {
            this.$store.commit('increase', {
              customcount: 10
            }); //每次加10
          },
          decrease() {
            this.$store.commit('decrease', {
              customcount: 5
            }); //每次减5
          }
        },
    

    也可以写成直接包含type属性,也就是mutations里的事件(例如:increase、decrease)

        methods: {
          increase() {
            this.$store.commit({
              type:"increase",
              customcount: 10
            }); //每次加10
          },
          decrease() {
            this.$store.commit({
              type:"decrease",
              customcount: 5
            }); //每次减5
          }
        },
    

    Mutation 需遵守 Vue 的响应规则

    既然 Vuex 的 store 中的状态是响应式的,那么当我们变更状态时,监视状态的 Vue 组件也会自动更新。这也意味着 Vuex 中的 mutation 也需要与使用 Vue 一样遵守一些注意事项:

    1. 最好提前在你的 store 中初始化好所有所需属性。
    2. 当需要在对象上添加新属性时,你应该
    • 使用 Vue.set(obj, 'newProp', 123), 或者

    • 以新对象替换老对象。例如,利用对象展开运算符可以这样写:

      state.obj = { ...state.obj, newProp: 123 }
      

    例如

    import Vue from 'vue'
    import Vuex from 'vuex'
    
    Vue.use(Vuex)
    
    export default new Vuex.Store({
      state: {
        people:[]
      },
      getters: {},
      mutations: {
        addNewProp(state,payload){
          
          //新增对象属性
          // Vue.set(state.people,"name","Tom");
    
          //用新对象替换老对象
          state.people= {...state.people, name: 'Jerry'} 
        }
      },
      actions: {},
      modules: {}
    })
    
    <template>
      <div>
        <input type="button" value="新增属性" @click="addNewProp" />
        <p>{{name}}</p>
      </div>
    </template>
    
    <script>
      import {
        mapState
      } from 'vuex'
      export default {
        methods: {
          addNewProp() {
            this.$store.commit('addNewProp');
          }
        },
        computed: {
          name() {
            return this.$store.state.people.name;
          },
        }}
    </script>
    

    Mutation必须是同步函数

    官网给的例子,当mutation触发的时候,回调函数还没有被调用,回调函数中进行的状态的改变都是不可追踪的。

    mutations: {
      someMutation (state) {
        api.callAsyncMethod(() => {
          state.count++
        })
      }
    }
    

    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')`
        })
      }
    }
    

    六、Action

    action类似于mutation,不同在于

    • Action可以提交mutation,而不是直接变更状态
    • Action可以包含任意异步操作
    import Vue from 'vue'
    import Vuex from 'vuex'
    
    Vue.use(Vuex)
    
    export default new Vuex.Store({
        state: {
            count: 0
        },
        getters: {},
        mutations: {
            // 增加
            increase(state) {
                state.count++
            },
            // 减少
            decrease(state) {
                state.count--
            }
        },
        actions: {
            // action函数接受一个与store实例具有相同方法和属性的context对象,调用context.commit 提交一个mutation
            increase(context) {
                context.commit('increase')
            }
        },
        modules: {}
    })
    

    action通过store.dispatch触发,action 则会提交 mutation,mutation 会对 state 进行修改,组件再根据 state 、getter 渲染页面。

    上边的action写法与之前直接提交mutation没有什么区别,但是action内部可以执行异步操作,而mutation只能是同步操作。

        // 执行异步操作
        actions: {
            increas({ commit }) {
                setTimeout(() => {
                    commit('increase')
                }, 1000)
            },
            decrease({ commit }) {
                setTimeout(() => {
                    commit('decrease')
                }, 1000)
            }
        }
    

    同时action也是支持荷载方式和对象方式进行分发:

    // 以载荷形式分发
    store.dispatch('increase', {
      amount: 10
    })
    
    // 以对象形式分发
    store.dispatch({
      type: 'increase',
      amount: 10
    })
    

    在组件中使用this.$store.dispatch('xxx') 分发 action,或者使用mapActions辅助函数将组件的methods映射为 store.dispatch 调用,

    <template>
      <div>
        <input type="button" value="+" @click="increase" />
        <input type="button" value="-" @click="decrease" />
        <p>计数:{{count}}</p>
      </div>
    </template>
    
    <script>
      import {
        mapActions
      } from 'vuex'
      import {
        mapState
      } from 'vuex'
      export default {
        // ...
        methods: {
          ...mapActions([
            'increase', // 将 `this.increase()` 映射为 `this.$store.dispatch('increment')`
          ]),
          ...mapActions({
            decrease: 'decrease' // 将 `this.decrease()` 映射为 `this.$store.dispatch('decrease')`
          })
        },computed:{
          ...mapState(["count"])
        }
      }
    </script>
    

    七、Module

    当应用变得复杂时,store对象会变得非常臃肿,vuex可以store分割成多个模块(Module),每个模块都拥有自己的state、mutation、action、getter。

    import Vue from 'vue'
    import Vuex from 'vuex'
    
    Vue.use(Vuex)
    
    
    const moduleA = {
        state: {
            a: 'a'
        },
        mutations: {},
        actions: {},
        getters: {}
    }
    
    const moduleB = {
        state: {
            b: 'b'
        },
        mutations: {},
        actions: {},
        getters: {}
    }
    export default new Vuex.Store({
        state: {
    
        },
        getters: {},
        mutations: {},
        actions: {},
        modules: { ma: moduleA, mb: moduleB }
    })
    
    <template>
      <div>
        <p>{{msg}}</p>
      </div>
    </template>
    
    <script>
      import {
        mapActions
      } from 'vuex'
      import {
        mapState
      } from 'vuex'
      export default {
        computed: {
          msg() {
            return this.$store.state.ma.a; 
          }
        }
      }
    </script>
    

    对于模块内部的mutation与getter,接受的第一个参数时模块的局部状态对象

    const moduleA = {
      state: () => ({
        count: 0
      }),
      mutations: {
        increase (state) {
          // 这里的 `state` 对象是模块的局部状态
          state.count++
        }
      },
    
      getters: {
        doubleCount (state) {
          return state.count * 2
        }
      }
    }
    

    对于模块内部的action,局部状态可以通过context.state暴露出来,根节点状态则为context.rootState

    const moduleA = {
      actions: {
        increaseIfOddOnRootSum ({ state, commit, rootState }) {
          if ((state.count + rootState.count) % 2 === 1) {
            commit('increment')
          }
        }
      }
    }
    

    模块内部的getter,根节点状态回作为第三个参数暴露出来

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

    命令空间

    默认情况下,mutations、actions、getters这些都是注册在全局上面的,可以直接调用,希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。

    首先新建一个app.js用来声明模块

    const state = {
        keyword: "",
    };
    const mutations = {
        SET_KEYWORD(state, payload) {
            state.keyword = payload
        },
        DEL_KEYWORD(state) {
            state.keyword = ""
        }
    };
    //action可以提交mutation,在action中可以执行store.commit,如果要使用某个action,需执行store.dispath
    const actions = {
        setKeyword({ commit }, value) {
            commit("SET_KEYWORD", value);
        },
        delKeyword({ commit }) {
            commit("DEL_KEYWORD");
        },
    
    };
    export const app = {
        namespaced: true,
        state,
        mutations,
        actions
    };
    

    然后在store.js里面引入

    import { app } from './app.js';
    
    export default new Vuex.Store({
      modules: {
        app: app
      },
    });
    

    使用action 根据模块注册的路径调用

    store.dispatch('app/delKeyword')
    

    在带命名空间的模块内访问全局内容(Global Assets)

    如果你希望使用全局 state 和 getter,rootStaterootGetters 会作为第三和第四参数传入 getter,也会通过 context 对象的属性传入 action。

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

    modules: {
      foo: {
        namespaced: true,
    
        getters: {
          // 在这个模块的 getter 中,`getters` 被局部化了
          // 你可以使用 getter 的第四个参数来调用 `rootGetters`
          someGetter (state, getters, rootState, rootGetters) {
            getters.someOtherGetter // -> 'foo/someOtherGetter'
            rootGetters.someOtherGetter // -> 'someOtherGetter'
          },
          someOtherGetter: state => { ... }
        },
    
        actions: {
          // 在这个模块中, dispatch 和 commit 也被局部化了
          // 他们可以接受 `root` 属性以访问根 dispatch 或 commit
          someAction ({ dispatch, commit, getters, rootGetters }) {
            getters.someGetter // -> 'foo/someGetter'
            rootGetters.someGetter // -> 'someGetter'
    
            dispatch('someOtherAction') // -> 'foo/someOtherAction'
            dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'
    
            commit('someMutation') // -> 'foo/someMutation'
            commit('someMutation', null, { root: true }) // -> 'someMutation'
          },
          someOtherAction (ctx, payload) { ... }
        }
      }
    }
    

    平时开发时只用到了这些,关于更多的module可以去撸官网。

    八、项目结构

    平时开发中使用下面的项目结构

    store
        modules
            a.js
            b.js
            c.js
        getters.js
        index.js
    

    a.js示例

    const state = {
        keyword: "",
    };
    const mutations = {
        SET_KEYWORD(state, payload) {
            state.keyword = payload
        },
        DEL_KEYWORD(state) {
            state.keyword = ""
        }
    };
    const actions = {
        setKeyword({ commit }, value) {
            commit("SET_KEYWORD", value);
        },
        delKeyword({ commit }) {
            commit("DEL_KEYWORD");
        },
    
    };
    export default {
        namespaced: true,
        state,
        mutations,
        actions
    };
    

    getters.js示例

    const getters = {
        keyword: state => state.a.keyword,
    };
    export default getters;
    

    index.js示例

    import Vue from "vue";
    import Vuex from "vuex";
    import getters from "./getters";
    const path = require("path");
    
    Vue.use(Vuex);
    
    const files = require.context("./modules", false, /\.js$/);
    let modules = {};
    files.keys().forEach(key => {
      let name = path.basename(key, ".js");
      modules[name] = files(key).default || files(key);
    });
    const store = new Vuex.Store({
      modules,
      getters
    });
    export default store;
    

    相关文章

      网友评论

          本文标题:Vuex 状态管理模式

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