Vuex 状态管理模式

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。




mapState 辅助函数

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





对象展开运算符

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




四、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 对象,可以以属性的方式访问




mapGetters 辅助函数

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




五、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,




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: {}
})



官网建议大多数情况下载荷应该是一个对象,这样可以包含多个字段并且记录的 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: {}
})



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 调用,




七、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 }
})



对于模块内部的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 状态管理模式)