一、Vuex
1.概念
专门在Vue中实现集中式状态(数据)管理的一个Vue插件(use引入),对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。”
多组件比如a、b、c、d进行事件共享的时候,都想要a里的数据,而且他们互为兄弟,按以前的方法就得用全局事件总线,但是那样的话组件就太多了。Vuex就专门解决共享数据的问题。单独放在一块区域,大家都想得到的数据就放它里面,大家都可以读、写,a改完之后的x=4,那么b在拿到x也是4。
2.什么时候使用Vuex
(1)多个组件依赖于同一状态
(2)来自不同组件的行为需要变更同一状态
3.求和案例:纯Vue版
<template><div><h2>当前求和为:{{sum}}</h2><select v-model.number="n"><option value="1">1</option><option value="2">2</option><option value="3">3</option><!-- 这里除了最开始设置的数字1,其他都是字符串所以都加不了了 --><!-- 所以加 :value,当成js表达式去解析 或者加.number强制转换--></select><button @click="increment">+</button><button @click="decrement">-</button><button @click="incrementOdd">当前求和为奇数再加</button><button @click="incrementWait">等一等再加</button></div>
</template><script>
export default {name: "myCount",data(){return {sum:0,n:1,//用户选择的数字}},methods:{increment(){this.sum+=this.n},decrement(){this.sum-=this.n},incrementOdd(){if(this.sum%2==1){this.sum+=this.n}},incrementWait(){setTimeout(()=>{this.sum+=this.n},500)}}
};
</script>
4.Vuex的工作原理图
(1)构成
构成Vuex的三个对象由store管理,而且这三个对象数据类型都是obj,dispatch、commit函数就是store里的,所以我们得让任何vc都能看见store。
(2)流程
Vue Components是组件们,比如说我要加2,然后这个数据传给dispatch函数,传参过去:第一个参数:你要做的动作,第二个参数:数字。
然后你写的函数在Actions(数据类型是Object)就会有一个函数跟它对应,然后你自己再去调用commit函数(提交),到了mutations(数据类型也是Object),commit里的jia,mutations也会有一个jia跟它对应,同时它还会拿到两个参数:state状态和2。
mutate不用你调用,只需要在mutations里的jia写一句state.sum+=2,底层自动加2,sum就不是0是2了,然后Vuex帮你开始渲染render,页面上的sum就变化了。
这样看起来好像Actions有点没用,但是上面是后端接口,因为有的时候给dispatch传只传了动作没有值,就得去后端问一下数据(值得要发送ajax请求才能得到的时候,就需要用到Actions了)。
如果传过来就有值的话,可以直接调用commit。
二、Vuex环境搭建
1.安装Vuex
npm i vuex@3,Vue2对应vuex3版本,Vue3对应vuex4版本
2.引入并Use一下vuex
import Vuex from 'Vuex'
Vue.use(Vuex)
use了vuex然后就可以在vm中创建store对象了
3.创建store
新建一个store文件夹,在里面新建一个index.js
注意:所有的import都是先被提到代码最上方先执行然后再执行其他代码
在main.js引入插件并use vuex插件必须在import store之前,所以在main里面不管把use怎么移动都是先import store会报错,干脆把use代码写在index.js里,index里没有vue就引入vue
main.js:
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue的生产提示
Vue.config.productionTip = false
import store from './store/index.js'
//创建vm
new Vue({el:'#app',render: h => h(App),beforeCreate() {Vue.prototype.$bus=this},store
})
index.js:
//该文件用于创建Vuex中最核心的store
//actions——响应组件中的动作
//引入Vue
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const actions={}
//mutations——用于操作数据
const mutations={}
//state——用于存储数据
const state={}
//创建store还得向外暴露
const store= new Vuex.Store({
actions,
mutations,
state
})export default store
4.求和案例:Vuex版本
(1)首先把sum放进vuex
//state——用于存储数据
const state = {sum: 0,
}
(2)插值语法
<h2>当前求和为:{{$store.state.sum}}</h2>
(3)在组件中的回调用dispatch
发给actions
methods:{increment(){this.$store.dispatch('jia',this.n)// $store是在vc身上的},
(4)actions来接数据,再调用commit函数
const actions = {jia(context,value) {context.commit('JIA',value)//context就是一个mini版的store// 调用commit函数,传过去方法和值}
}
(5)mutations来接数据
我们不是说actions里的东西要想用就得保证mutations里面也得有吗,所以写完actions里的东西之后在mutations里也加上,为了区分mutations里面都用大写
//mutations——用于操作数据
const mutations = {JIA(state,value) {state.sum+=value}
}
我们写的state里面只有一个sum=0,但是如果输出一下就能发现实际上还有getter和setter之类的,是vue给我们封装的,类似data
基础版index:
const actions = {// jia(context,value) {// context.commit('JIA',value)// //context就是一个mini版的store// // 调用commit函数,传过去方法和值// },// jian(context,value) {// context.commit('JIAN',value)// },//这两个都没必要绕弯再去找mutations,直接去找jiaOdd(context,value) {if(context.state.sum%2){context.commit('JIA',value)//这儿不用写JIAODD因为调用的都是jia,然后下面也就不用加JIAODD了}},jiaWait(context,value) {setTimeout(()=>{context.commit('JIA',value)},500)},
}
//mutations——用于操作数据
const mutations = {JIA(state,value) {state.sum+=value},JIAN(state,value) {state.sum-=value}
}
基础版myCount:
methods: {increment() {//this.$store.dispatch("jia", this.n);this.$store.commit("JIA", this.n);// $store是在vc身上的},decrement() {//this.$store.dispatch("jian", this.n);this.$store.commit("JIAN", this.n);//直接去找mutations},incrementOdd() {this.$store.dispatch("jiaOdd", this.n);},incrementWait() {this.$store.dispatch("jiaWait", this.n);},},
};
注意点:1.JIAODD、JIAWAIT里的方法都是JIA不用再单独写一个
2.JIA、JIAN都可以省略找Actions,直接去找Mutations
3.如果在actions直接写context.state.num+=value也能奏效,不用再找Mutations,但是!!这样开发者工具就失效了,所以还是得按照标准写。
4.业务逻辑写在组件里不写在action行不行?拿发票报销举例子,在组件里写就是调用第一个地儿然后传单号,然后调用第、、、个地儿再传单号很麻烦,直接告诉actions我要报销然后传单号,剩下的事让actions去解决。
三、Vuex开发者工具
跟vue位置一样,像表一样的图案就是Vuex的开发者工具,每一栏操作的后面有三个按钮,第一个按钮下载一样的是点击哪个,这个和它所有之前的都合并作为基底
第二个按钮是取消某一层,而且取消之后它后面的那些层也就都没了,就像盖楼一样,三层塌了上面的都得没
第三个按钮是时光穿梭到某个时候,展示那个时候的数据
哪条最绿说明页面正在呈现哪层
展示栏的右上角是导出和导入操作步骤
四、配置项
1.getters配置项
1、概念:当state中的数据需要经过加工后再使用时,可以使用getters加工,类似Vue中的计算属性computed与data。
2、类似于计算属性,但是好多组件都可以用,computer属性只能当前属性用(逻辑复杂或者逻辑还想复用的时候就用getters,得写返回值)
//用于将state的数据进行加工
const getters={bigSum(state){return state.sum*10}
}
配置完记得暴露,调用一下:
<h2>当前和乘十为:{{ $store.getters.bigSum }}</h2>
2.mapstate与mapGetters
(1)mapstate
当我用插值语法用index中state中的数据的时候,都还得写$store.state、、、,写多了很麻烦,vuex为我们准备了一个方法:
首先先引入:
import {mapState} from 'vuex'
computed: {...mapState({ sum: "sum", school: "school", subject: "subject" }),},
...是es6语法,因为mapState也是一个对象,不能{ }里面再直接套一个对象{},又不是插值语法,...最后再加逗号,里面第一个是上面div要用的,第二个是index里的命名,自动就给你补齐$store.state了,然后在div里直接用:
<h2>我在{{ school }}里学习{{ subject }}</h2>
a:a才能简写为a,但是num:‘num’不能简写(对象写法不能简写)
但是数组方法可以简写:注意:这是前后两个名字相同的情况下!!
一个名字两个用途,既可以用在index,也可以用在myCount组件
...mapState(['sum','school', 'subject' ]),
(2)mapGetters
用法一样
...mapGetters(['bigSum ' ]),
//...mapGetters({bigSum='bigSum'}),
3.mapActions与mapMutations
(1)mapMutations
借助mapMutations生成对应的方法,方法中会调用commit去联系Mutations
methods: {// increment() {// //this.$store.dispatch("jia", this.n);// this.$store.commit("JIA", this.n);// // $store是在vc身上的// },// decrement() {// //this.$store.dispatch("jian", this.n);// this.$store.commit("JIAN", this.n);// //直接去找mutations// },...mapMutations({increment:'JIA',decrement:'JIAN'}),
但是这么写还有点问题,确实被调用了,但是没有告诉人家n是多少,人家就默认传过去的value是鼠标点击事件,所以调用函数的时候得把n传过去
<button @click="increment(n)">+</button><button @click="decrement(n)">-</button>
(2)mapActions
借助mapActions生成对应的方法,方法中会调用dispatch去联系Actions
// incrementOdd() {// this.$store.dispatch("jiaOdd", this.n);// },// incrementWait() {// this.$store.dispatch("jiaWait", this.n);// },...mapActions({ incrementOdd: "jiaOdd", incrementWait: "jiaWait" }),
五、多组件共享数据
mapstate引入state里其他组件的数据,然后直接插值语法用就行
computed: {...mapState(['personList','sum'])},
<h2>上方组件的求和为:{{sum}}</h2>
六、Vuex模块化+命名空间
目的
让代码更好维护,让多种数据分类更加明确。
(1)Count组件
我们的action、mutations、state包含了两个组件的内容,如果内容很多的话,写一块就很乱,可以把他们分开写
//求和相关的配置
const countOptions={actions:{jiaOdd(context,value) {if(context.state.sum%2){context.commit('JIA',value)//这儿不用写JIAODD因为调用的都是jia,然后下面也就不用加JIAODD了}},jiaWait(context,value) {setTimeout(()=>{context.commit('JIA',value)},500)},},mutations:{JIA(state,value) {state.sum+=value},JIAN(state,value) {state.sum-=value},},state:{sum: 0,school:'bj',subject:'qd',},getters:{bigSum(state){return state.sum*10}},
}
//人员相关的配置
const personOptions={actions:{},mutations:{ADD_PERSON(state,value){//value就是人的对象obj state.personList.unshift(value)//往前放}},state:{personList:[{id:'001',name:'tt'}]},getters:{},
}
const store = new Vuex.Store({modules:{a:countOptions,b:personOptions//此时store里面就剩a、b了,mapState下的sum根本找不着了}
})
再想在组件里用可不能直接写num、school啥的了,因为store里只有a和b,就得带着a、b引用
computed: {//...mapState({ sum: "sum", school: "school", subject: "subject" }),...mapState(['a', 'b']),
<h2>当前求和为:{{ a.sum }}</h2><h2>当前和乘十为:{{ bigSum }}</h2><h2>我在{{ a.school }}里学习{{ a.subject }}</h2><h3>下方组件的总人数是:{{b.personList.length}}</h3>
哪儿都带着a、b调用吧又很麻烦,可以直接在computed里面给这些东西加a. b.
computed: {...mapState('a',['sum','school','subject']), ...mapState('b',['personList']),
但是这个用法得配合开启命名空间namespaced:true,用,否则不生效,自己定义的配置后面都得写
const personOptions={namespaced:true,
包括方法啥的也得改是谁谁下的,后面是数组是对象都行
methods: {...mapMutations('a',{ increment: "JIA", decrement: "JIAN" }),...mapActions('a',{ incrementOdd: "jiaOdd", incrementWait: "jiaWait" }),},
(2)person组件
前面提到的要改正的不再多说,person要想调用ADD_PERSON方法,在前面加???/
this.$store.commit('b/ADD_PERSON',personObj)
又添加了一些功能并且把两个js分出去单独写了
index.js
//该文件用于创建Vuex中最核心的store
//actions——响应组件中的动作
//引入Vue
import Vue from 'vue'
import Vuex from 'vuex'
import personOptions from './myPerson'
import countOptions from './myCount'
Vue.use(Vuex)//创建store还得向外暴露
const store = new Vuex.Store({modules:{a:countOptions,b:personOptions//此时store里面就剩a、b了,mapState下的sum根本找不着了}
})export default store
myPerson.vue
<template><div><h1>人员列表</h1><h3>我想返回的列表的第一个人的名字是:{{ firstPersonName }}</h3><h2>上方组件的求和为:{{ a.sum }}</h2><input type="text" placeholder="请输入姓名" v-model="name" /><button @click="add">添加</button><button @click="addWang">添加一个姓王的人</button><button @click="addPersonServer">添加一个人</button><ul><li v-for="p in b.personList" :key="p.id">{{ p.name }}</li></ul></div>
</template><script>
import { mapState } from "vuex";
import { nanoid } from "nanoid";
export default {name: "myPerson",data() {return {name: "",};},computed: {...mapState(["a", "b"]),firstPersonName() {return this.$store.getters["b/firstPersonName"];//有.就不能有/,可以用[]代替.},},methods: {add() {const personObj = { id: nanoid(), name: this.name };this.$store.commit("b/ADD_PERSON", personObj);this.name = "";//输完之后框清除},addWang() {const personObj = { id: nanoid(), name: this.name };this.$store.dispatch('b/addPersonWang', personObj);this.name = "";},addPersonServer(){this.$store.dispatch('b/addPersonServer')}},
};
</script><style>
</style>
myCount.vue
<template><div><h2>当前求和为:{{ sum }}</h2><h2>当前和乘十为:{{ bigSum }}</h2><h2>我在{{ school }}里学习{{ subject }}</h2><h3>下方组件的总人数是:{{personList.length}}</h3><select v-model.number="n"><option value="1">1</option><option value="2">2</option><option value="3">3</option><!-- 这里除了最开始设置的数字1,其他都是字符串所以都加不了了 --><!-- 所以加 :value,当成js表达式去解析 或者加.number强制转换--></select><button @click="increment(n)">+</button><button @click="decrement(n)">-</button><button @click="incrementOdd(n)">当前求和为奇数再加</button><button @click="incrementWait(n)">等一等再加</button></div>
</template><script>
import { mapState, mapGetters, mapMutations, mapActions } from "vuex";
export default {name: "myCount",data() {return {n: 1, //用户选择的数字};},computed: {...mapState('a',['sum','school','subject']), ...mapState('b',['personList']), //sum: "sum", school: "school", subject: "subject" }),//...mapState('a',['a', 'b']),...mapGetters('a',['bigSum']),},methods: {...mapMutations('a',{ increment: "JIA", decrement: "JIAN" }),...mapActions('a',{ incrementOdd: "jiaOdd", incrementWait: "jiaWait" }),},
};
</script><style>
button {margin-left: 5px;
}
</style>
myCount.js
//求和相关的配置
const countOptions={namespaced:true,actions:{jiaOdd(context,value) {if(context.state.sum%2){context.commit('JIA',value)//这儿不用写JIAODD因为调用的都是jia,然后下面也就不用加JIAODD了}},jiaWait(context,value) {setTimeout(()=>{context.commit('JIA',value)},500)},},mutations:{JIA(state,value) {state.sum+=value},JIAN(state,value) {state.sum-=value},},state:{sum: 0,school:'bj',subject:'qd',},getters:{bigSum(state){return state.sum*10}},
}
export default countOptions
myPerson.js
import axios from "axios"
//import { response } from "express"
import { nanoid } from "nanoid"//人员相关的配置
const personOptions={namespaced:true,actions:{addPersonWang(context,value){if(value.name.indexOf('王')==0){context.commit('ADD_PERSONSE',value)//如果包括王而且位置还是第一个}else{alert ('添加的人得姓王!')}},addPersonServer(context){axios.get('http://api.uixsj.cn/hitokoto/get?type=social').then(response=>{context.commit('ADD_PERSONSE',{id:nanoid(),anme:response.data})},error=>{alert(error.message)}) }},mutations:{ADD_PERSONSE(state,value){//value就是人的对象obj state.personList.unshift(value)//往前放}},state:{personList:[{id:'001',name:'tt'}]},getters:{firstPersonName(state){return state.personList[0].name//这里的state是局部的}},
}
export default personOptions