文章目录
Vuex是什么?
一个程序里面的状态管理模式,它是集中式存储所有组件的状态的小仓库,并且保持我们存储的状态以一种可以预测的方式发生变化。
Vuex应用背景
假如是多级嵌套关系,你可以使用父子组件传参进行解决,虽有些麻烦,但好在可以解决;对于兄弟组件或者关系更复杂组件之间,就很难办了,虽然可以通过各种各样的办法解决,可实在很不优雅,而且等项目做大了,代码就会变得很复杂,实在令人心烦。
Vuex应用场景
如果你不需要开发大型的单页应用,此时你完全没有必要使用vuex,假如你的项目达到了中大型应用的规模,Vuex 将会成为自然而然的选择。
只有在多界面之间共享的状态,我们才将其交给vuex来管理。比如:
1.用户登录状态: 用户名, 头像, 昵称等等. 很多页面可能都会用到用户的基本信息, 像这些统一的信息, 我们就可以放在统一的地方进行管理了。
2.token: 用户登录的令牌, 某些接口必须有令牌才能访问, 那么这些几口就需要共享token。
3.商品收藏, 购物车中的物品等. 我们在各个界面都可以添加商品搜藏, 都可以加购, 这时候, 就可以将其放入到vuex里面。
放在vuex中, 不仅能够共享状态, 还能够实时响应
Vuex的使用
1.安装
进入项目,在命令行中输入安装指令:npm install vuex --save
2.新建store文件
在项目src路径下创建store文件夹,然后创建index.js文件,文件内容如下:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
name1: '彩虹一号',
name2: '彩虹二号',
name3: '彩虹三号',
}
})
export default store
3.main.js实例挂载store
引入index的store,进行实例挂载。
import Vue from 'vue'
import App from './App'
import router from './router'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import 'xe-utils'
import VXETable from 'vxe-table'
import 'vxe-table/lib/style.css'
import store from './store'
Vue.use(ElementUI)
Vue.config.productionTip = false
Vue.use(VXETable)
new Vue({
el: '#app',
router,
store, // 挂载store
components: { App },
template: '<App/>'
})
4.测试Vuex数据
在App.vue中写入
<script>
export default {
name: "App",
data() {
return {
};
},
mounted() {
console.log(this.$store.state.name1); // 彩虹一号
},
computed: {},
};
</script>
Vuex的核心部分
state => 基本数据
getters => 从基本数据派生的数据
mutations => 提交更改数据的方法,同步!
actions => 像一个装饰器,包裹mutations,使之可以异步。
modules => 模块化Vuex
1.state
上面vuex功能已经提到state,官方建议我们以上操作this.$store.state.XXX最好放在计算属性中,当然,这样可以让你的代码看起来更优雅一些,就像这样:在App.vue中写入
<script>
export default {
name: "App",
data() {
return {
};
},
mounted() {
console.log(this.getMyName); // 彩虹一号
},
computed: {
getMyName() {
return this.$store.state.name1;
},
},
};
</script>
是不是每次都写this.$store.state.XXX让你感到厌烦,你实在不想写这个东西怎么办,当然有解决方案,就像下面这样:在App.vue中写入
<script>
import { mapState } from "vuex"; // 从vuex中导入mapState
export default {
name: "App",
data() {
return {
};
},
mounted() {
console.log(this.name1); // 彩虹一号
},
computed: {
...mapState(["name1"]), // 经过解构后,自动就添加到了计算属性中,此时就可以直接像访问计算属性一样访问它
},
};
</script>
当然,你甚至可以在解构的时候给它赋别名,取外号,就像这样:
// 赋别名的话,这里接收对象,而不是数组;相当于...mapState({ myName: state=>state.name1}),
...mapState({ myName: 'name1' }),
当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以在App.vue中使用 mapState 辅助函数帮助我们生成计算属性。
<script>
import { mapState } from "vuex"; // 从vuex中导入mapState
export default {
name: "App",
data() {
return {
school: "麻省理工学院",
};
},
mounted() {
console.log(this.myName1); // 彩虹一号
console.log(this.myName2); // 彩虹二号
console.log(this.myName3); // 麻省理工学院彩虹三号
},
computed: {
...mapState({
myName1: "name1", // 第一种写法,简写,等效于第二种写法
myName2: (state) => state.name2, // 第二种写法,简单处理状态值
// myName3: (state) => { // 错误写法,箭头函数的this指针并没有指向vue实例
// return this.school + state.name3;
// },
myName3: function (state) {
return this.school + state.name3;
},
}),
},
};
</script>
mapState的数组跟对象写法
数组写法:
...mapState(["name1","name2","name3"])
对象写法:
...mapState({
myName1: "name1",
myName2: (state) => state.name2,
myName3: function (state) {
return this.school + state.name3;
},
}),
2.Getter
getter是 store 的计算属性,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
首先,在store对象中增加getters属性
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
name1: "彩虹一号",
name2: "彩虹二号",
name3: "彩虹三号",
},
getters: {
getWords1(state) {
return `我的名字叫${state.name1}`;
},
getWords2(state) {
return `我的名字叫${state.name2}`;
},
getWords3(state) {
return `我的名字叫${state.name3}`;
},
}
});
export default store;
接受state作为第一个参数,通过this.$store.getters调用。
mounted() {
console.log(this.$store.getters.getWords1); // 我的名字叫彩虹一号
},
也可以使用辅助函数mapgetters将 store 中的 getter 映射到局部计算属性,在App.vue中先引入mapGetters 。
<script>
import { mapState, mapGetters } from "vuex";
export default {
name: "App",
data() {
return {
school: "麻省理工学院",
};
},
mounted() {
console.log(this.getWords1, this.getWords2, this.getWords3); // 我的名字叫彩虹一号,我的名字叫彩虹二号,我的名字叫彩虹三号
console.log(this.getWs1, this.getWs2, this.getWs3); // 我的名字叫彩虹一号,我的名字叫彩虹二号,我的名字叫彩虹三号
},
computed: {
...mapGetters(["getWords1", "getWords2", "getWords3"]), // 数组写法
...mapGetters({ // 重命名写法
getWs1: "getWords1",
getWs2: "getWords2",
getWs3: "getWords2",
}),
},
};
</script>
3.Mutation
mutation类似于事件,可以改变state的状态,必须同步操作,在组件中改变状态时通过this.$store.commit(‘事件名’,传递的参数)调用mutation里面的事件。Vuex中不能直接对store的状态进行赋值修改。
在store中定义mutation:
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
name1: "彩虹一号",
name2: "彩虹二号",
name3: "彩虹三号",
},
getters: {
getWords1(state) {
return `我的名字叫${state.name1}`;
},
getWords2(state) {
return `我的名字叫${state.name2}`;
},
getWords3(state) {
return `我的名字叫${state.name3}`;
}
},
mutations: {
changeName(state) {
state.name1 = '银河一号';
}
}
});
export default store;
我们不能直接this. s t o r e . m u t a t i o n s . c h a n g e N a m e ( ) 来 调 用 , V u e x 规 定 必 须 使 用 t h i s . store.mutations.changeName() 来调用,Vuex 规定必须使用 this. store.mutations.changeName()来调用,Vuex规定必须使用this.store.commit 来触发对应 type 的方法:在App.vue中commit改变state中name1的值。
mounted() {
console.log(`旧值:${this.$store.state.name1}`); // 彩虹一号
this.$store.commit('changeName');
console.log(`新值:${this.$store.state.name1}`); // 银河一号
},
以上是简单实现mutations的方法,是没有传参的,如果我们想传不固定的参数怎么办?mutations中定义changeNameAgain函数:
mutations: {
changeName(state) {
state.name1 = "银河一号";
},
changeNameAgain(state, name) {
state.name1 = name;
}
}
App.vue组件内commit修改:
mounted() {
this.$store.commit('changeNameAgain','北斗一号');
console.log(`新值:${this.$store.state.name1}`); // 北斗一号
},
Mutations里面的函数必须是同步操作,不能包含异步操作!
就像最开始的mapState和mapGetters一样,我们在组件中可以使用mapMutations以代替this.$store.commit(‘XXX’),是不是很方便呢?
记住在App.vue引入mapMutations
<script>
import { mapState, mapGetters, mapMutations } from "vuex";
export default {
name: "App",
mounted() {
// this.changeNameAgain('银河一号')` 映射为`this.$store.commit('changeNameAgain','银河')
this.changeNameAgain('银河一号');
console.log(this.$store.state.name1) // 银河一号
},
methods: {
...mapMutations(["changeNameAgain"]),
},
computed: {},
};
</script>
同理,如下赋别名写法也是可以的:
mounted() {
this.changeName1('银河一号');
console.log(this.$store.state.name1) // 银河一号
},
methods:{
...mapMutations({ changeName1: 'changeNameAgain' }), // 赋别名的话,这里接收对象,而不是数组
}
Mutation常量类型 – 概念
在mutation中, 我们定义了很多事件类型(也就是其中的方法名称),当我们的项目增大时, Vuex管理的状态越来越多,需要更新状态的情况越来越多,那么意味着Mutation中的方 法越来越多。
方法过多, 使用者需要花费大量的经历去记住这些方法, 甚至是多个文件间来回切换, 查看方法名称, 甚至如果不 是复制的时候, 可能还会出现写错的情况。mutation函数名 和 $store.commit(‘函数名’) 都改变,这样保证 错误率降低。
在store文件夹中新建js文件,名字叫做mutations-types,用来存放将要在mutations中定义的函数名。
mutations-types存放将要在mutations中定义的函数名如下:
export const CHANGE_NAME_ONE = 'CHANGE_NAME_ONE'
export const CHANGE_NAME_TWO = 'CHANGE_NAME_TWO'
export const CHANGE_NAME_THREE = 'CHANGE_NAME_THREE'
之后进入index.js文件,首先导入mutations-types.js文件,在mutations中定义函数。注意使用常量定义函数时需要使用该格式:[函数名] (参数){函数具体内容}
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
import * as types from "./mutations-types";
// import { CHANGE_NAME_ONE, CHANGE_NAME_TWO, CHANGE_NAME_THREE } from "./mutations-types"; // 也可以按需引入
const store = new Vuex.Store({
state: {
name1: "彩虹一号",
name2: "彩虹二号",
name3: "彩虹三号"
},
mutations: {
[types.CHANGE_NAME_ONE](state) {
state.name1 = "银河一号";
},
// [CHANGE_NAME_ONE](state) { // 对应按需引入写法的函数
// state.name1 = "银河一号";
// },
[types.CHANGE_NAME_TWO](state, payload) {
state.name2 = payload;
},
[types.CHANGE_NAME_THREE](state, payload) {
state.name3 = payload.name;
}
}
});
export default store;
App.vue中修改:可以看到打印的this.name1, this.name2, this.name3值为:银河一号 银河二号 银河三号
<script>
import { mapState, mapGetters, mapMutations } from "vuex";
export default {
name: "App",
data() {
return {};
},
mounted() {
this.CHANGE_NAME_ONE();
this.CHANGE_NAME_TWO("银河二号");
this.CHANGE_NAME_THREE({ name: "银河三号" });
console.log(this.name1, this.name2, this.name3); // 银河一号 银河二号 银河三号
},
methods: {
...mapMutations([
"CHANGE_NAME_ONE",
"CHANGE_NAME_TWO",
"CHANGE_NAME_THREE",
]),
},
computed: {
...mapState(["name1", "name2", "name3"]),
},
};
</script>
不引用辅助函数mapState, mapMutations的写法如下(注意引入mutations-types):
<script>
import * as types from "./store/mutations-types";
export default {
name: "App",
data() {
return {};
},
mounted() {
console.log(types)
this.$store.commit(types.CHANGE_NAME_ONE);
this.$store.commit(types.CHANGE_NAME_TWO, "银河二号");
this.$store.commit(types.CHANGE_NAME_THREE, { name: "银河三号" });
console.log(
this.$store.state.name1,
this.$store.state.name2,
this.$store.state.name3
); // 银河一号 银河二号 银河三号
},
};
</script>
4.Actions
Actions存在的意义是假设你在修改state的时候有异步操作,vuex作者不希望你将异步操作放在Mutations中,所以就给你设置了一个区域,让你放异步操作,这就是Actions。
我们再看一下这张图,action的声明周期就是上面那一块,它在组件里被dispatch到,然后进入action,执行异步操作,异步操作(promise)总有一个结果的,出结果后再决定提不提交mutation
Action 类似于 mutation,不同在于Action 提交的是 Mutation,不是直接变更状态。Action 可以包含任意异步操作。
在store的index.js文件定义Actions的方法:
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
import * as types from "./mutations-types";
const store = new Vuex.Store({
state: {
name1: "彩虹一号"
},
mutations: {
[types.CHANGE_NAME_ONE](state, payload) {
state.name1 = payload.name;
}
},
actions: {
changeNameOne(content) {
// 默认第一个参数为content
return new Promise((resolve, reject) => {
setTimeout(() => {
content.commit(types.CHANGE_NAME_ONE, { name: "银河一号" });
resolve(content.state.name1);
}, 1000);
});
}
}
});
export default store;
在App.vue通过提交Actions方法修改state.name1的值。
<script>
import * as types from "./store/mutations-types";
export default {
name: "App",
data() {
return {};
},
async mounted() {
console.log(this.$store.state.name1); // 彩虹一号
await this.$store.dispatch("changeNameOne");
console.log(this.$store.state.name1); // 银河一号
this.$store.dispatch("changeNameOne").then((res) => {
console.log(res); // 银河一号
});
},
};
</script>
当然也可以通过传参的方式修改,此时修改下actions的changeNameOne方法,改成传参形式,content后面为传过来的参数。
actions: {
changeNameOne(content, payload) {
return new Promise((resolve, reject) => {
setTimeout(() => {
content.commit(types.CHANGE_NAME_ONE, payload);
resolve(content.state.name1);
}, 1000);
});
}
}
此时App.vue传参修改state.name1的值。
<script>
import * as types from "./store/mutations-types";
export default {
name: "App",
data() {
return {};
},
async mounted() {
console.log(this.$store.state.name1); // 彩虹一号
let awaitRes= await this.$store.dispatch("changeNameOne", {name: '银河一号'});
console.log(awaitRes); // 银河一号
console.log(this.$store.state.name1); // 银河一号
},
};
</script>
上面的这句代码
let awaitRes= await this.$store.dispatch("changeNameOne", {name: '银河一号'});
其实已经执行了,直接这样写:this.$store.dispatch(“changeNameOne”, {name: ‘银河一号’})就够了,程序员偶尔还是要皮一下的。
让我们看看Actions中的content,Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。
// context可以理解为state的父级,上一级,包含着state中的所有属性
context: {
state, // 等同于store.$state,若在模块中则为局部状态
rootState, //等同于store.$state,只存在模块中
commit, //等同于store.$commit
dispatch, //等同于store.$dispatch
getters, //等同于store.$getters
//等等
},
在store/index.js中的actions里面,方法的形参可以直接将commit解构出来,这样可以方便后续操作,同理state 也可以结构。
const store = new Vuex.Store({
state: {
name1: "彩虹一号"
},
mutations: {
[types.CHANGE_NAME_ONE](state, payload) {
state.name1 = payload.name;
}
},
actions: {
changeNameOne({ commit, state }, payload) { // 形参解构入参{ commit, state }
return new Promise((resolve, reject) => {
setTimeout(() => {
commit(types.CHANGE_NAME_ONE, payload);
resolve(state.name1);
}, 1000);
});
}
}
});
此时App.vue执行,一样的结果。
<script>
import * as types from "./store/mutations-types";
export default {
name: "App",
data() {
return {};
},
async mounted() {
console.log(this.$store.state.name1); // 彩虹一号
let awaitRes= await this.$store.dispatch("changeNameOne", {name: '银河一号'});
console.log(awaitRes); // 银河一号
console.log(this.$store.state.name1); // 银河一号
},
};
</script>
我们也可以在actions里面触发内部actions方法:
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
import * as types from "./mutations-types";
const store = new Vuex.Store({
state: {
name1: "彩虹一号"
},
mutations: {
[types.CHANGE_NAME_ONE](state, payload) {
state.name1 = payload.name;
}
},
actions: {
changeNameOne({ commit, state }, payload) {
return new Promise((resolve, reject) => {
commit(types.CHANGE_NAME_ONE, payload);
resolve(state.name1);
});
},
changeNameOneAgain({ dispatch, state }, payload) {
return new Promise((resolve, reject) => {
setTimeout(() => {
dispatch("changeNameOne", payload); // dispatch触发actions内部的changeNameOne方法
resolve(state.name1);
}, 1000);
});
}
}
});
export default store;
App.vue执行,一秒后执行结果state.name1值为北斗一号
<script>
import * as types from "./store/mutations-types";
export default {
name: "App",
data() {
return {};
},
async mounted() {
console.log(this.$store.state.name1); // 彩虹一号
await this.$store.dispatch("changeNameOneAgain", {name: '北斗一号'});
console.log(this.$store.state.name1); // 北斗一号
},
};
</script>
你也可以采用mapActions的方式,把相关的actions解构到methods中。
<script>
import { mapActions } from "vuex";
export default {
name: "App",
data() {
return {};
},
async mounted() {
console.log(this.$store.state.name1); // 彩虹一号
await this.changeNameOneAgain({ name: "北斗一号" });
console.log(this.$store.state.name1); // 北斗一号
},
methods: {
...mapActions(["changeNameOneAgain"]),
},
};
</script>
5.module
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割。
下面的链接挺详细的,可以参考下:
module的使用