一、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