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 一样遵守一些注意事项:
- 最好提前在你的 store 中初始化好所有所需属性。
- 当需要在对象上添加新属性时,你应该
-
使用
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,rootState
和 rootGetters
会作为第三和第四参数传入 getter,也会通过 context
对象的属性传入 action。
若需要在全局命名空间内分发 action 或提交 mutation,将 { root: true }
作为第三参数传给 dispatch
或 commit
即可。(官网例子)
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;
网友评论