什么是 Vuex ?
官方的解释
Vuex
是一个专为Vue.js
应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
个人总结
看了上面一段文字我想行大多数人都是晕头转向的,官方的话通常都是晦涩难懂的,因为需要保证权威性和正确性。所有我通过我自己的理解给大家简单的理解一下:
所谓的Vuex
其实就是一个为Vue.js
设计的数据仓库,就是把各个组件公用的数据放到一个仓库里面进行统一的管理,这样既使得非父子组件间的数据共享变得简单明了,也让程序变得更加可维护(将数据抽离了出来),而且只要仓库里面的数据发生了变化,在其他组件里面数据被引用的地方也会自动更新。
语法解释
state
注意:① state 是响应式的,当我们变更状态时,监视状态的 Vue 组件也会自动更新;② 因为 state 是响应式的,最好提前在你的 state 中初始化好所有所需属性,当需要在对象上添加新属性时,可以使用对象展开运算符。(例如:state.obj = { ...state.obj, newProp: 123 }
)
getter
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:(state, getters) => { };(通过属性访问的定义方式)
- 参数:1.getter: getter 函数名;2.state: 表示 store 的状态集;3.getters:可选,表示 getter 的集合,可以在一个 getter 中通过 getters 访问其他 getter。
- store.getters.getter;(通过属性访问)
- 参数:1.getter:表示 getter函数名。
- getter:(state, getters) => (args) => {};(通过方法访问的定义方式)
- 参数:1.getter: getter 函数名;2.state: 表示 store 的状态集;3.getters:可选,表示 getter 的集合,可以在一个 getter 中通过 getters 访问其他 getter;4.args:方法的参数。
- store.getters.getter(args);(通过方法访问)
- 参数:1.getter:表示 getter 函数名;2.args:getter 函数中所调用方法的参数。
注意:① getter 会暴露为 store.getters 对象,你可以以属性的形式访问这些值;② getter 在通过属性访问时是作为 Vue 的响应式系统的一部分缓存其中的;③ getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果。
Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
mutation
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
// 回调函数 (handler)
increment (state) {
// 变更状态
state.count++
}
}
})
- 语法:
- handler( state, payload ){ };
- 参数: ①. state:表示 store 的状态集; ②. payload:可选,表示 mutation 的提交载荷。
- store.commit(type, payload); (载荷的提交方式)
- 参数: ①. type:表示 mutation 的事件类型,通过 type 唤醒 handler,且 type 的名称与 handler 名称保持一致; ②. payload:表示 mutation 的提交载荷。
- store.commit({ type: typeName, args }); (对象风格的提交方式)
- 参数: ① { type: typeName, args }:这整个对象都作为载荷 payload 传给 mutation 函数;② typeName 表示 mutation 的事件类型;③ args:可选,传递的参数数据。
注意:① 每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler) ;② 在大多数情况下,载荷 payload 应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读;③ 建议使用常量代替 mutation 事件类型,可以使 linter 之类的工具发挥作用,同时把这些常量放在单独文件更易读。
action
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
- 语法:
- handler( context, payload ){ };
- 参数:① context:与 store 实例具有相同方法和属性,当我们在之后介绍到 Modules 时,你就知道 context 对象为什么不是 store 实例本身了;② payload:表示 action 的提交载荷。
- store.dispatch(type, payload);(载荷的提交方式)
- 参数:① type:表示 action 的事件类型;② payload:可选,表示 action 的提交载荷。
- store.dispatch({ type: typeName, args });(对象风格的提交方式)
- 参数: ① { type: typeName, args }:这整个对象都作为载荷 payload 传给 action 函数;② typeName 表示 action 的事件类型;③ args:可选,传递的参数数据。
注意:① mapActions
辅助函数将组件的 methods 映射为 store.dispatch
调用;② store.dispatch
可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch
仍旧返回 Promise,所以可以组合 action;③ 一个 store.dispatch
在不同模块中可以触发多个 action 函数。在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。
构建项目和引入 Vuex
vue2 + vuex3
1.安装 vuex: npm install vuex --save
2.引入 vuex
① 在src
目录下新建一个store
文件夹,并在store
目录下新建index.js
。
② 在src/store/index.js
里面使用vuex
,添加如下代码:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex); // 显式地通过 Vue.use() 来安装 Vuex
const store = new Vuex.Store({
state: {
count: 0,
},
mutations: {
increment (state) {
state.count++;
};
}
});
export default store; //导出store
③ 在main.js中引入store,然后全局注入一下,这样就可以在任何一个组件里面使用它了。
/**
* 为了在 Vue 组件中访问 `this.$store` property, 你需要为 Vue 实例提供创建好的 store。
* Vuex 提供了一个从根组件向所有子组件,以 store 选项的方式 “注入” 该 store 的机制。
*/
import Vue from 'vue';
import App from './App';
import router from './router';
import store from './store';
Vue.config.productionTip = false;
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
});
④ 使用 state,以下涉及 state 的多种写法。
// src/store/index.js:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
count: 10,
name: '香蕉',
cycle: '一年'
}
});
export default store;
// src/App.vue:
<template>
<div class="wrapper">
<p>{{ countVal }}</p>
<p>{{ $store.state.count }}</p>
<p>解析后的count:{{ resolveCount }}</p>
<p>品名:{{ name }}</p>
<p>品名:{{ nameAlias }}</p>
<p>生长周期:{{ cycle }}</p>
</div>
</template>
<script>
import { mapState } from 'vuex';
export default {
data() {
return {};
},
computed: {
...mapState([
// 映射 this.cycle 为 this.$store.state.cycle
'cycle'
]),
...mapState({
name: (state) => state.name,
// 传字符串参数 'name' 等同于 `state => state.name`
nameAlias: 'name',
resolveCount(state) {
return state.count + 20;
}
}),
countVal() {
return this.$store.state.count;
}
}
};
</script>
⑤ 使用 getter,以下涉及 getter 的多种写法。
// src/store/index.js:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
years: 1,
todos: [
{ id: 1, name: '香蕉', done: true },
{ id: 2, name: '橘子', done: true },
{ id: 3, name: '苹果', done: false }
]
},
mutations: {},
actions: {},
getters: {
getYears: (state) => {
return state.years;
},
filterTodos: (state) => {
return state.todos.filter((todo) => todo.done);
},
filterTodosCount: (state, getters) => {
return getters.filterTodos.length;
},
getTodosById: (state) => (id) => {
return state.todos.find((todo) => todo.id === id);
}
}
});
export default store;
// src/App.vue:
<template>
<div class="wrapper">
<p>{{ getYears }}</p>
<p>{{ $store.getters.filterTodos }}</p>
<p>{{ getFilterTodos }}</p>
<p>{{ getFilterTodosCount }}</p>
<p>{{ doneTodosCount }}</p>
<p>{{ getTodosById }}</p>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
data() {
return {};
},
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'getYears'
]),
...mapGetters({
// 将 getter 属性另取名为 `doneTodosCount`, 把 `this.doneTodosCount` 映射为
// `this.$store.getters.filterTodosCount`。
doneTodosCount: 'filterTodosCount'
}),
// 以属性的形式访问
getFilterTodos() {
return this.$store.getters.filterTodos;
},
// 以属性的形式访问
getFilterTodosCount() {
return this.$store.getters.filterTodosCount;
},
// 以方法的形式访问
getTodosById() {
return this.$store.getters.getTodosById(2);
}
},
methods: {}
};
</script>
⑥ 使用 mutation,以下涉及 mutation 的多种写法。
// src/store/index.js:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
count: 10,
},
mutations: {
increment(state, params) {
const { addVal } = params;
state.count = state.count + addVal;
},
decrease(state, params) {
const { decreaseVal } = params;
state.count = state.count - decreaseVal;
},
multiple(state, params) {
const { multipleVal } = params;
state.count = state.count * multipleVal;
}
}
});
export default store;
// src/App.vue:
<template>
<div class="wrapper">
<p>{{ $store.state.count }}</p>
<button @click="handleAdd">增加</button>
<button @click="handleIncrease">增加2</button>
<button @click="handleDecrease">减少</button>
<button @click="handleReduce">减少2</button>
<button @click="handleMultiple">倍数</button>
<button @click="multiple({ multipleVal: 3 })">三倍</button>
</div>
</template>
<script>
import { mapMutations, mapState } from 'vuex';
export default {
data() {
return {};
},
methods: {
...mapMutations([
'decrease' // 1.将 `this.decrease()` 映射为 `this.$store.commit('decrease')`;
]), // 2.将 `this.decrease(payload)` 映射为 `this.$store.commit('decrease', payload)`
// (`mapMutations` 也支持载荷)。
...mapMutations({
reduce: 'decrease', // 将 `this.reduce()` 映射为 `this.$store.commit('decrease')`
multiple: 'multiple'
}),
handleAdd() {
this.$store.commit('increment', { addVal: 1 });
},
handleIncrease() {
this.$store.commit({ type: 'increment', addVal: 1 });
},
handleDecrease() {
this.decrease({ decreaseVal: 1 });
},
handleReduce() {
this.reduce({ decreaseVal: 1 });
},
handleMultiple() {
this.multiple({ multipleVal: 2 });
}
}
};
</script>
⑦ 使用 actions,以下涉及 action 的多种写法。
// src/store/index.js:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
count: 10,
},
mutations: {
increment(state, params) {
const { addVal } = params;
state.count = state.count + addVal;
},
decrease(state, params) {
const { decreaseVal } = params;
state.count = state.count - decreaseVal;
},
multiple(state, params) {
console.log(params);
const { multipleVal } = params;
state.count = state.count * multipleVal;
}
},
actions: {
async incrementAction(context, params) {
const { commit } = context;
commit('increment', params);
},
decreaseAction(context, params) {
const { commit } = context;
return new Promise((resolve) => {
setTimeout(() => {
commit({ type: 'decrease', ...params });
resolve();
}, 3000);
});
},
multipleAction({ commit }, params) {
commit('multiple', params);
},
getMultipleAction(context) {
return new Promise((resolve) => {
resolve(2);
});
},
async handleComAction(context) {
const { dispatch, commit } = context;
await dispatch('decreaseAction', { decreaseVal: 1 });
commit('multiple', await getDoubleData());
}
}
});
async function getDoubleData() {
return new Promise((resolve) => {
resolve({ multipleVal: 2 });
});
}
export default store;
// src/App.vue:
<template>
<div class="wrapper">
<p>{{ $store.state.count }}</p>
<button @click="handleAdd">增加</button>
<button @click="handleIncrease">增加2</button>
<button @click="handleDecrease">减少</button>
<button @click="handleReduce">减少2</button>
<button @click="handleMultiple">倍数</button>
<button @click="multipleAction({ multipleVal: 3 })">三倍</button>
<button @click="handleCom">先增加后翻倍</button>
</div>
</template>
<script>
import { mapActions, mapState } from 'vuex';
export default {
data() {
return {};
},
methods: {
...mapActions([
'handleComAction',
'decreaseAction' // 1.将 `this.decreaseAction()` 映射为
]), // `this.$store.dispatch('decreaseAction')`; 2.将
// `this.decreaseAction(amount)` 映射为
...mapActions({ // `this.$store.dispatch('decreaseAction', payload)`(`mapMutations` 也支持载荷)。
reduceAction: 'decreaseAction', // 将 `this.reduceAction()` 映射为
multipleAction: 'multipleAction' // `this.$store.dispatch('decreaseAction')`。
}),
handleAdd() {
this.$store.dispatch('incrementAction', { addVal: 1 });
},
handleIncrease() {
this.$store.dispatch({ type: 'incrementAction', addVal: 1 });
},
handleDecrease() {
this.decreaseAction({ decreaseVal: 1 });
},
handleReduce() {
this.reduceAction({ decreaseVal: 1 });
},
handleMultiple() {
this.multipleAction({ multipleVal: 2 });
},
handleCom() {
this.handleComAction();
}
}
};
</script>
Module(vuex 模块化)
如果模块没有开启命名空间:① 分发 mutation 或 action 的时候,不需要加模块前缀;② 如果不同模块中有 mutation 或 action 重名的情况,在分发 mutation 或 action 后,这个重名的 handler 函数都将根据模块列表的顺序依次触发;③ 通常我们会开启模块命名空间。
// src/store/index.js:
import Vue from 'vue';
import Vuex from 'vuex';
import moduleA from './modules/a';
import moduleB from './modules/b';
Vue.use(Vuex);
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
});
export default store;
/**
* 注意: 1.默认 namespaced: false, 没有开启命名空间;
*/
// src/store/modules/a.js:
const moduleA = {
state: {
name: '模块A',
count: 1,
years: 1
},
mutations: {
changeYearA(state) {
const { val } = params;
state.years = val;
}
},
actions: {}
};
export default moduleA;
// src/store/modules/b.js:
const moduleB = {
namespaced: true, // 开启了命名空间
state: {
name: '模块B',
count: 2,
years: 2
},
mutations: {
changeYears(state, params) {
const { val } = params;
state.years = val;
},
changeName(state, params) {
const { name } = params;
state.name = name;
},
changeCount(state, params) {
console.log(params);
const { count } = params;
state.count = count;
}
},
actions: {
changeYearsAction({ commit }, params) {
commit('changeYears', params);
}
}
};
export default moduleB;
// src/App.vue:
<template>
<div class="wrapper">
<p>名称:{{ $store.state.a.name }}</p>
<p>名称2:{{ $store.state.b.name }}</p>
<p>数量: {{ a.count }}</p>
<p>{{ a.name }}</p>
<p>模块a的年限:{{ $store.state.a.years }}</p>
<p>{{ name }}</p>
<p>{{ b.count }}</p>
<p>获取名称:{{ getName }}</p>
<p>年限:{{ years }}</p>
<p>获取年限:{{ getYears }}</p>
<button @click="changeYears({ val: 3 })">更改年限</button>
<button @click="handleName()">更改名称</button>
<button @click="handleCount()">更改数量</button>
<button @click="changeYearA">更改模块A的年限</button>
<button @click="changeYA({ val: 6 })">更改模块A的年限2</button>
<button @click="changeYearsAction({ val: 5 })">更改模块B的年限</button>
</div>
</template>
<script>
import { mapActions, mapMutations, mapState } from 'vuex';
export default {
data() {
return {};
},
computed: {
...mapState(['a']),
...mapState({
name: (state) => state.a.name
}),
getName() {
return this.$store.state.a.name;
},
...mapState(['b']),
// 第一个参数是模块的空间名称字符串
...mapState('b', {
years: (state) => state.years
}),
getYears() {
return this.years;
}
},
methods: {
...mapMutations({
changeYA: 'changeYearA'
}),
...mapMutations('b', [
'changeYears' // -> this.changeYears()
]),
...mapMutations(['b/changeCount']), // -> this['b/changeCount']()
...mapActions('b', [
'changeYearsAction' // -> this.changeYearsAction()
]),
handleName() {
this.$store.commit('b/changeName', { name: '还是模块B' });
},
handleCount() {
this['b/changeCount']({ count: 5 });
},
changeYearA() {
this.$store.commit('changeYearA', { val: 6 }); // a 模块没有开启命名空间,所以不用加模块名
}
}
};
</script>
使用 createNamespacedHelpers
创建基于某个命名空间辅助函数。它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数。
// src/App.vue:
<template>
<div class="wrapper">
<p>名称:{{ $store.state.a.name }}</p>
<p>名称2:{{ $store.state.b.name }}</p>
<p>数量: {{ a.count }}</p>
<p>{{ a.name }}</p>
<p>模块a的年限:{{ $store.state.a.years }}</p>
<p>{{ name }}</p>
<p>{{ b.count }}</p>
<p>获取名称:{{ getName }}</p>
<p>年限:{{ years }}</p>
<p>获取年限:{{ getYears }}</p>
<button @click="changeYears({ val: 3 })">更改年限</button>
<button @click="handleName()">更改名称</button>
<button @click="handleCount()">更改数量</button>
<button @click="changeYearA">更改模块A的年限</button>
<button @click="changeYA({ val: 6 })">更改模块A的年限2</button>
<button @click="changeYearsAction({ val: 5 })">更改模块B的年限</button>
</div>
</template>
<script>
import { mapMutations, mapState, createNamespacedHelpers } from 'vuex';
const b = createNamespacedHelpers('b');
export default {
data() {
return {};
},
computed: {
...mapState(['a']),
...mapState({
name: (state) => state.a.name
}),
getName() {
return this.$store.state.a.name;
},
...mapState(['b']),
...b.mapState({
years: (state) => state.years
}),
getYears() {
return this.years;
}
},
methods: {
...mapMutations({
changeYA: 'changeYearA'
}),
...b.mapMutations(['changeYears', 'changeCount', 'changeName']),
...b.mapActions(['changeYearsAction']),
handleName() {
this.changeName({ name: '还是模块B' });
},
handleCount() {
this.changeCount({ count: 5 });
},
changeYearA() {
this.$store.commit('changeYearA', { val: 6 });
}
}
};
</script>