这是我参与8月更文挑战的第4天,活动详情查看: 8月更文挑战” 。
Vuex是什么?
Vuex是一个专为Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。 这个状态我们可以理解为在data中的属性,需要共享给其他组件使用的部分。也就是说,是我们需要共享的data,使用vuex进行统一集中式的管理。
1. Vuex的构成
图中绿色虚线框起来的部分就是Vuex的核心。后续我们会详细介绍图中的核心部分。
每一个Vuex应用的核心就是stor(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state) 。每个vue应用有且仅有一个store对象。
const store = new Vuex.Store({
state: {},
getters: {},
mutations: {},
actions: {},
modules: {}
});
可见store是Vuex.Store这个构造函数new出来的实例。在构造函数中可以传一个对象参数。这个参数中可以包含5个对象:
- state – 存放状态
- getters – state的计算属性
- mutations – 更改state状态的逻辑,同步操作
- actions – 提交mutation,异步操作
- mudules – 将store进行模块化,每一个module有自己的state, getters, mutations, actions等
Vuex和单纯的全局对象有以下两点不同:
- Vuex的状态存储是响应式的。当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会相应地得到高效更新。
- 不能直接改变store中的状态。改变store中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
什么情况下应该使用Vuex?
- 小应用不建议使用Vuex,因为小项目使用Vuex可能会比较繁琐冗余。
- 中大型单页应用,因为要考虑如何更好地在组件外部管理状态,Vuex将会成为自然而然的选择。
Vuex的使用与核心概念
在介绍Vuex的核心概念之前, 使用vue-cli初始化了一个项目,就会生成如下格式的代码,接下来以代码的形式来说明Vuex的核心概念。
- main.js
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
Vue.config.productionTip = false;
Vue.prototype.$bus = new Vue({
router,
store,
render: h => h(App)
}).$mount("#app");
- store文件夹index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules: {}
});
1. State
state是Vuex中的公共的状态。
便于理解: 可以将state看作是所有组件的data, 用于保存所有组件的公共数据。
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
count: 0
},
mutations: {},
actions: {},
modules: {}
});
在组件中使用
<template>
<!-- vuex基本使用 -->
<div>{{ count }}</div>
</template>
<script>
export default {
data() {
return {};
},
computed: {
count() {
return this.$store.state.count;
}
}
};
</script>
2. Getters
有时候我们需要从store中的state中派生出一些状态,此时可以用到getters,getters可以看作是store的计算属性。有两种访问方式,方式一通过属性访问,接受state作为其第一个参数,也可以接受其他getter作为第二个参数。不支持用户随意传入参数。方式二通过方法访问,通过让getter返回一个函数,来实现getter传参。
便于理解: 可以将getters属性理解为所有组件的computed属性,也就是计算属性。通过属性访问时getters的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。通过方法访问时,每次都会去进行调用,而不会缓存结果。
state: {
count: 0,
list: [
{ id: 1, title: "产品名称一", status: 1 },
{ id: 2, title: "产品名称二", status: 0 },
{ id: 3, title: "产品名称三", status: 1 }
]
},
getters: {
// 访问方式一:通过属性访问
// getter接受state作为其第一个参数,也可以接受其他getter作为第二个参数。不支持用户随意传入参数
// 一个参数:
filterList: state => {
return state.list.filter(item => item.status == 1);
},
// 两个参数
listLen: (state, getters) => {
return getters.filterList.length;
},
// 访问方式二:通过方法访问
// 通过让getter返回一个函数,来实现getter传参
getListById: state => id => {
return state.list.filter(item => item.id == id);
}
},
在组件使用
computed: {
count() {
return this.$store.state.count;
},
list() {
return this.$store.getters.filterList;
},
listLen() {
return this.$store.getters.listLen;
},
getListById() {
return this.$store.getters.getListById(1);
}
}
3. Mutations
mutations里面是同步更改state中状态的逻辑。更改Vuex中的state的唯一方法是提交mutation。每个mutation都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler) 。这个回调函数就是我们实际进行状态更改的地方,回调函数的第一个参数是state, 第二参数是mutation的载荷(payload), 也就是自定义的参数。可以使用常量替代mutation事件类型
便于理解: 可以将mutaions理解为store中的methods。mutation必须是同步函数,如果非要写成异步后果就是在view视觉看到count值改变了但是在store的state属性中count的值并没有改变。
mutations: {
increament(state) {
state.count++;
},
// 提交载荷(Payload)
increamentPayload(state, payload) {
state.count += payload.n;
},
// 使用ES2015风格的计算属性命名功能来使用一个常量作为函数名
[DOUBLE_INCREAMENT](state) {
state.count += 2;
},
// 错误示范:mutation不支持异步提交,这是错误的写法,在view视觉看到count值改变了但是在store的state属性中count的值并没有改变
increamentAsyncError(state) {
setTimeout(() => {
state.count++;
}, 1000);
}
},
type.js
export const DOUBLE_INCREAMENT = "doubleIncreament";
在组件中使用
import { DOUBLE_INCREAMENT } from "./type";
...
methods: {
increament() {
this.$store.commit("increament");
},
// 提交载荷
increamentPayload() {
this.$store.commit("increamentPayload", { n: 10 });
},
// 对象风格的提交方式
increamentOhter() {
this.$store.commit({
type: "increamentPayload",
n: 10
});
},
// 使用ES2015风格的计算属性命名功能来使用一个常量作为函数名
doubleIncreament() {
this.$store.commit("doubleIncreament");
},
// 错误示范
increamentAsyncError() {
this.$store.commit("increamentAsyncError");
},
}
4. Actions
因为mutations中只能是同步操作,但是在实际的项目中,会有异步操作,那么actions就是为了异步操作而设置的。这样,就变成了在action中去提交mutation,然后在组件的methods中去提交action。
actions: {
increamentAsync(context) {
setTimeout(() => {
context.commit("increament");
}, 1000);
},
increamentAsyncPayload(context, payload) {
setTimeout(() => {
context.commit("increamentPayload", payload);
}, 1000);
},
// 使用参数解构,简化代码
increamentAsync2({ commit }) {
setTimeout(() => {
commit("increament");
}, 1000);
}
},
在组件中使用
methods: {
// 分发action
increamentAsync() {
this.$store.dispatch("increamentAsync");
},
// 以载荷形式分发
increamentAsyncPayload() {
this.$store.dispatch("increamentAsyncPayload", { n: 20 });
},
// 以对象方式分发
increamentAsyncOther() {
this.$store.dispatch({
type: "increamentAsyncPayload",
n: 10
});
},
// 使用参数解构,简化代码
increamentAsync2() {
this.$store.dispatch("increamentAsync2");
}
}
二者的区别:
actions提交的是mutations而不是直接变更状态actions中可以包含异步操作,mutations中绝对不允许出现异步actions中的回调函数的第一个参数是context, 是一个与store实例具有相同属性和方法的对象。可以使用ES2015 的 参数解构actions提交的时候使用的是dispatch分发函数,mutations则是用commit提交函数。
5. Modules
module是为了将store拆分为一个个小模块,这么做的目的是因为当应用变得非常复杂时,store对象就有可能变的很大、很臃肿,分成模块的话方便管理。每个module拥有自己的state, getters, mutation, action。对于模块内部的mutation和getter,接受的第一个参数是模块的局部状态state。
小结
Vuex是通过全局注入store对象,来实现组件间的状态共享。在大型复杂的项目中(多级组件嵌套),需要实现一个组件更改某个数据,多个组件自动获取更改后的数据进行业务逻辑处理,这时候使用vuex比较合适。假如只是多个组件间传递数据,使用vuex未免有点大材小用,其实只用使用组件间常用的通信方法即可。
附:演示完整的源码
- store index.js
import Vue from "vue";
import Vuex from "vuex";
import { DOUBLE_INCREAMENT } from "./type";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
count: 0,
list: [
{ id: 1, title: "产品名称一", status: 1 },
{ id: 2, title: "产品名称二", status: 0 },
{ id: 3, title: "产品名称三", status: 1 }
]
},
getters: {
// 访问方式一:通过属性访问
// getter接受state作为其第一个参数,也可以接受其他getter作为第二个参数。不支持用户随意传入参数
// 一个参数:
filterList: state => {
return state.list.filter(item => item.status == 1);
},
// 两个参数
listLen: (state, getters) => {
return getters.filterList.length;
},
// 访问方式二:通过方法访问
// 通过让getter返回一个函数,来实现getter传参
getListById: state => id => {
return state.list.filter(item => item.id == id);
}
},
mutations: {
increament(state) {
state.count++;
},
// 提交载荷(Payload)
increamentPayload(state, payload) {
state.count += payload.n;
},
// 使用ES2015风格的计算属性命名功能来使用一个常量作为函数名
[DOUBLE_INCREAMENT](state) {
state.count += 2;
},
// 错误示范:mutation不支持异步提交,这是错误的写法,在view视觉看到count值改变了但是在store的state属性中count的值并没有改变
increamentAsyncError(state) {
setTimeout(() => {
state.count;
}, 1000);
}
},
actions: {
increamentAsync(context) {
setTimeout(() => {
context.commit("increament");
}, 1000);
},
increamentAsyncPayload(context, payload) {
setTimeout(() => {
context.commit("increamentPayload", payload);
}, 1000);
},
// 使用参数解构,简化代码
increamentAsync2({ commit }) {
setTimeout(() => {
commit("increament");
}, 1000);
}
},
modules: {}
});
- store type.js
export const DOUBLE_INCREAMENT = "doubleIncreament";
- test.vue
<script>
export default {
data() {
return {};
},
computed: {
count() {
return this.$store.state.count;
},
list() {
return this.$store.getters.filterList;
},
listLen() {
return this.$store.getters.listLen;
},
getListById() {
return this.$store.getters.getListById(1);
}
},
methods: {
increament() {
this.$store.commit("increament");
},
// 提交载荷
increamentPayload() {
this.$store.commit("increamentPayload", { n: 10 });
},
// 对象风格的提交方式
increamentOhter() {
this.$store.commit({
type: "increamentPayload",
n: 10
});
},
// 使用ES2015风格的计算属性命名功能来使用一个常量作为函数名
doubleIncreament() {
this.$store.commit("doubleIncreament");
},
// 错误示范
increamentAsyncError() {
this.$store.commit("increamentAsyncError");
},
// 分发action
increamentAsync() {
this.$store.dispatch("increamentAsync");
},
// 以载荷形式分发
increamentAsyncPayload() {
this.$store.dispatch("increamentAsyncPayload", { n: 20 });
},
// 以对象方式分发
increamentAsyncOther() {
this.$store.dispatch({
type: "increamentAsyncPayload",
n: 10
});
},
// 使用参数解构,简化代码
increamentAsync2() {
this.$store.dispatch("increamentAsync2");
}
}
};
</script>