一、Vuex
1.Vuex的环境搭建
1.1安装vuex
安装:npm i vuex@3
注意Vue2一定要安装vuex3,如果是vue3可以直接npm i vuex
安装的是vuex4,可以去package.json
文件里看下vue的版本是啥,千万别输错了,不然会陷入痛苦的报错……
1.2.创建store文件
创建文件:src/store/index.js
在此文件中引入插件并使用vuex插件,使用vuex插件必须在引入store之前,如果在main.js中引入和使用vuex的话,由于js文件里所有的import语句都会提升到最开始执行,所以会报错滴。总结:引入store必须在Vue.use(Vuex)
之后
//该文件用于创建Vuex中最为核心的store
//引入Vue
import Vue from 'vue';
//引入Vuex
import Vuex from 'vuex';//引入插件并使用插件
Vue.use(Vuex); //使用插件后就可以在vm,vc里使用store配置项
//准备actions,用于响应组件中的动作
const actions = {};
//准备mutations,用于操作数据(state)
const mutations = {};
//准备state,用于存储数据
const state = {};
//创建store
const store = new Vuex.Store({
actions: actions,
mutations, //简写
state //简写
});
//导出store
export default store;
1.3.main.js引入store
JS执行的时候会把import提升到顶部,与摆放顺序无关,如果放在main.js里 import store from './store'
无论放到哪里都会比Vue.use(Vuex)
先执行,要想把 Vue.use(Vuex)
要放到实例化之前只有放进index.js
//js文件里所有的import语句都会提升到最开始执行
// 引入Vue
import Vue from 'vue';
// 引入App
import App from './App.vue';
Vue.config.productionTip = false;
//引入store
import store from './store/index.js';
// 创建一个Vue实例
new Vue({
el: '#app',
store: store, //或者直接写store
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this; //创建全局事件总线
}
});
这样的话,vm和所有vc就都能碰到$store
了
2.Vuex的工作原理
2.1看图说话
这个工作流程用一个通俗的例子来解释:VC就是顾客,actions就是服务员,mutations就是厨师,state就是菜的原料。顾客(VC)来到店里,先告诉(dispatch)服务员(actions)要吃啥菜,然后服务员把菜单给(commit)厨师(mutations),完了之后厨师对菜的原料(state)来个加工(mutate),最后再给(render)顾客(VC)
2.2用例子来解释工作原理
使用vuex实现求和案例
1、首先应该把求和数据给 vuex的state对象
const state = {
sum: 0, //初始化数据
};
2、页面上插值语法就该用
<h1>当前求和为:{{ $store.state.sum }}</h1>
//插值语法可以不用this
3、在组件中的回调就可以用dispatch
发给actions
(点菜),注意如果没有业务逻辑,直接commit
可以跳过actions
直接给mutations
add() {
this.$store.dispatch('jia', this.addnum);
//如果没有业务逻辑,直接commit可以跳过actions直接给mutations
//this.$store.commit('JIA', this.addnum);
},
4、在actions
中使用jia
这个函数来接(处理顾客需求),并且使用commit
发给mutations
(需求告诉厨师)。这里jia
函数会接到两个参数,第一个参数是浓缩版的$store
,方便你在这里调用commit
把东西给mutations
,第二个参数是组件那边传过来的数据。
jia(context, value) {
//第一个参数是浓缩版的$store,方便你在这里调用commit把东西给mutations
//第二个参数是传过来的数据
context.commit('JIA', value);
},
5、在mutations
中使用JIA
这个函数来接(做菜),一般都写成actions
对应函数的大写,意思是mutations
更nb。也是接两个参数,第一个参数是state对象,第二个参数是传过来的数据。
JIA(state, value) {
//第一个参数是state对象,第二个参数是传过来的数据
console.log('mutations中的JIA被调用了 ', state, value);
state.sum += value;
},
6、最后通过state中匹配的setter,实现响应式,把数据更新到页面(上菜)。
2.3Count.vue文件
<template>
<div>
<h1>当前求和为:{{ $store.state.sum }}</h1>
<select v-model.number="addnum">
<option value="1" checked>1</option><!-- 不写冒号就是字符串但可以v-model.number -->
<option value="2">2</option> <!-- 不写冒号就是字符串但可以v-model.number -->
<option value="3">3</option> <!-- 不写冒号就是字符串但可以v-model.number -->
</select>
<button @click="add">+</button>
<button @click="subtract">-</button>
<button @click="oddAdd">当前求和为奇数再加</button>
<button @click="waitAdd">等1秒再加</button>
</div>
</template>
<script>
export default {
name: 'Count',
props: ['title'],
data() {
return {
addnum: 1
}
},
methods: {
add() {
//如果没有业务逻辑,直接commit给mutations
this.$store.commit('JIA', this.addnum);
},
subtract() {
this.$store.commit('JIAN', this.addnum);
},
oddAdd() {
//如果有业务逻辑,先dispatch给actions,再commit给mutations
this.$store.dispatch('oddAdd', this.addnum);
},
waitAdd() {
this.$store.dispatch('waitAdd', this.addnum);
}
},
};
</script>
4.store/index.js文件
其实actions
中context
也可以调用dispatch
传给下一个actions
中的方法,用于逻辑很复杂的情况,要一个一个传,这样层次分明,等处理差不多了再传给mutations
//该文件用于创建Vuex中最为核心的store
//引入Vue
import Vue from 'vue';
//引入Vuex
import Vuex from 'vuex';//引入插件并使用插件
Vue.use(Vuex); //使用插件后就可以在vm,vc里使用store配置项
//准备actions,用于响应组件中的动作,写业务逻辑
const actions = {
oddAdd(context, value) {
//第一个参数是浓缩版的$store,方便你在这里调用commit把东西给mutations
//第二个参数是传过来的数据
console.log('处理了一些逻辑:oddAdd');
context.dispatch('demo1', value); //如果逻辑多,可以多层往下传(第一个服务员)
},
demo1() {
console.log('处理了一些复杂的逻辑:demo1');
context.dispatch('demo2', value); //如果逻辑多,可以多层往下传(第二个服务员)
},
demo2() {
if (context.state.sum % 2)
context.commit('JIA', value); //传到这(第三个服务员),再提交给mutations
},
waitAdd(context, value) {
setTimeout(() => {
context.commit('JIA', value);
}, 1000);
},
};
//准备mutations,用于操作数据(state)
const mutations = {
JIA(state, value) {
//第一个参数是state对象,第二个参数是传过来的数据
console.log('mutations中的JIA被调用了 ', state, value);
state.sum += value;
},
JIAN(state, value) {
state.sum -= value;
}
};
//准备state,用于存储数据
const state = {
sum: 0, //初始化数据
};
//创建并导出store
export default new Vuex.Store({
actions: actions,
mutations, //简写
state //简写
});
3.一些配置项
3.1getters配置项
1、概念:当state中的数据需要经过加工后再使用时,可以使用getters加工,类似Vue中的计算属性computed。
2、使用:在store\index.js中追加getters配置,写函数,页面读的时候读的是返回值,这点其实也和计算属性很像。
......
//准备 getters ---用于将state中的数据进行加工
const getters = {
bigSum(state){
return state.sum * 10
}
}
//创建并暴露store
export default new Vuex.Store({
......
getters
})
3、组件中读取数据:$store.getters.bigSum
<h1>当前求和放大十倍后为:{{ $store.getters.bigSum }}</h1>
4、其实state
就类似于data
,getters
就类似computed
2.mapstate与mapGetters
首先导入四个map方法 import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
mapstate
与mapGetters
用到computed
里,mapMutations
,mapActions
用到methods
里
要说之前啊,我们要往页面上放state中的数据,还得$store.state.xxx
,或者 $store.getters.xxx
<h1>当前求和为:{{ $store.state.sum }}</h1>
<h1>当前求和放大十倍后为:{{ $store.getters.bigSum }}</h1>
<h1>我在{{ $store.state.school }}学习{{ $store.state.subject }}</h1>
真的是非常麻烦啊,想简单点写,就可以用一下子计算属性:
computed: {
//靠程序员亲自写计算属性来实现state插值语法编码方便
sum() {
return this.$store.state.sum;
},
school() {
return this.$store.state.school;
},
subject() {
return this.$store.state.subject;
},
bigSum() {
return this.$store.getters.bigSum;
}
},
诶这样就可以这么写了:
<h1>当前求和为:{{ sum }}</h1>
<h1>当前求和放大十倍后为:{{ bigSum }}</h1>
<h1>我在{{ school }}学习{{ subject }}</h1>
但是实际上computed这些东西复用性很差,vuex给我们提供了一个mapState
和mapGetters
方法:用于帮助我们把state
和getters
中的数据映射为计算属性,具体写法是下面这样滴,用到了扩展运算符,先复习一下扩展运算符:
let obj1 = {x:100, y:200};
let obj2 = {
a:1,
...obj1,
b:2
}
console.log(obj2); //{a: 1, x: 100, y: 200, b: 2}
然后下面这样写就欧了,这里注意对象写法可以任意起名,键对应计算属性方法名
,值对应state中的数据名
,如果方法名和数据名一样
,就可以用数组形式简写
computed: {
//靠mapState函数来实现上面的代码(对象写法)sum: 'sum'没有简写,sum: sum才可以简写
...mapState({ sum: 'sum', school: 'school', subject: 'subject' }),
//靠mapState函数来实现上面的代码(数组写法)
...mapState(['sum', 'school', 'subject']),
// 靠mapGetters函数来实现上面的代码(对象和数组写法)
...mapGetters({bigSum: 'bigSum'}),
...mapGetters(['bigSum'])
},
3.mapActions与mapMutations
要搁以前呐,我们这个要从组件直接用commit传数据给mutations,或者用dispatch传给actions,都需要在methods里配置:
methods: {
//程序员费老大劲写的传mutations代码
add() {
//如果没有业务逻辑,直接commit给mutations
this.$store.commit('JIA', this.addnum);
},
subtract() {
this.$store.commit('JIAN', this.addnum);
},
//程序员费老大劲写的传actions代码
oddAdd() {
//如果有业务逻辑,先dispatch给actions,再commit给mutations
this.$store.dispatch('oddAdd', this.addnum);
},
waitAdd() {
this.$store.dispatch('waitAdd', this.addnum);
},
},
但是如今有了mapActions
方法:用于帮助我们生成与actions
对话的方法,即:包含$store.dispatch(xxx)
的函数,还有mapMutations
方法:用于帮助我们生成与mutations
对话的方法,即:包含$store.commit(xxx)
的函数。
也是有两种写法, 对象写法中,键是方法名
,值是传给mutations
或者actions
的方法名
,如果方法名和传的方法名一样可以简写为数组形式,一些细节见注释
methods: {
//使用mapMutations实现程序员费老大劲写的代码联系mutations
...mapMutations({ add: 'JIA', subtract: 'JIAN' }),
//如果键值名字一样,可以用数组简写,记得页面也要同步
//...mapMutations(['JIA', 'JIAN']),
//使用mapActions实现程序员费老大劲写的代码联系actions
...mapActions({ oddAdd: 'oddAdd', waitAdd: 'waitAdd' }),
// ...maoActions(['oddAdd', 'waitAdd']),
},
备注:mapActions
与mapMutations
使用时,其实创建的程序员费老大劲写的代码是不一样的,这两个玩意儿创建的东西是不带数据的,它创建的是这个:
add(value) {
this.$store.commit('JIA', value);
},
若需要传递参数value
,需要:在模板中绑定事件时传递好参数,否则参数value
是事件对象。
<button @click="increment(n)">+</button>
<button @click="decrement(n)">-</button>
<button @click="incrementOdd(n)">当前求和为奇数再加</button>
<button @click="incrementWait(n)">等一等再加</button>
##4.多组件共享数据
就是各个组件都能拿到state里的数据,然后用来用去了,很方便
5.Vuex模块化+命名空间(项目常用)
好难理解。。
1.这是啥
如果我们写的state,actions什么的是服务于多个种类的,比如有管加法的,有管人员的,这样放到一起很乱,所以可以把它们拆开
2.目的
让代码更好维护,让多种数据分类更加明确。
3.使用方式:
可以都写到index.js里,也可以每个命名空间分别拆成多个js文件
const countAbout = {
namespaced:true,//开启命名空间
state:{x:1},
mutations: { ... },
actions: { ... },
getters: {...}
}
}
const personAbout = {
namespaced:true,//开启命名空间
state:{ ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
countAbout,
personAbout
}
})
(1)开启命名空间后,组件中读取state数据
//方式一:自己直接读取
this.$store.state.personAbout.list
//方式二:借助mapState读取:
...mapState('countAbout',['sum','school','subject']),
// 前边加个参数,意思是读取countAbout 里面的 sum,school.....
(2)开启命名空间后,组件中读取getters数据
//方式一:自己直接读取
this.$store.getters['personAbout/firstPersonName'] //很奇葩
//方式二:借助mapGetters读取:
...mapGetters('countAbout',['bigSum'])
(3)开启命名空间后,组件中调用dispatch
如果不写namespaced则直接写addPersonWang
就可以,但是开启了命名空间,必须要加上这个名字在前边,否则会报[vuex] unknown action type: addPersonWang
的错误,而且前边这个名字必须和Vuex.Store({})
配置项中的名字一致。
//方式一:自己直接dispatch
this.$store.dispatch('personAbout/addPersonWang',person)
//方式二:借助mapActions:
...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
(4)开启命名空间后,组件中调用commit
//方式一:自己直接commit
this.$store.commit('personAbout/ADD_PERSON',person)
//方式二:借助mapMutations:
...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
4.冷静,读读代码吧
(1)index.js
//引入Vue
import Vue from 'vue';
//引入Vuex
import Vuex from 'vuex';//引入插件并使用插件
Vue.use(Vuex);
//关于计数的相关配置
import countAbout from './Count'
//关于人员的相关配置
import personAbout from './Person'
//创建并导出store
export default new Vuex.Store({
modules: {
countAbout: countAbout,
personAbout //重名简写
}
});
(2)Count.js
export default {
namespaced: true,
state: {
sum: 0, //初始化数据
school: 'HNUST',
subject: '前端',
},
getters: {
bigSum(state) {
return state.sum * 10;
}
},
actions: {
oddAdd(context, value) {
//第一个参数是浓缩版的$store,方便你在这里调用commit把东西给mutations
//第二个参数是传过来的数据
context.commit('JIA', value);
},
waitAdd(context, value) {
setTimeout(() => {
context.commit('JIA', value);
}, 1000);
},
},
mutations: {
JIA(state, value) {
//第一个参数是state对象,第二个参数是传过来的数据
console.log('mutations中的JIA被调用了 ', state, value);
state.sum += value;
},
JIAN(state, value) {
state.sum -= value;
},
}
}
(3)Count.vue
这里边有个在actions里发送ajax请求,可以研究研究
<template>
<div>
<h1>当前求和为:{{ sum }}</h1>
<h2>当前求和放大十倍后为:{{ bigSum }}</h2>
<h3>我在{{ school }}学习{{ subject }}</h3>
<select v-model.number="addnum">
<option value="1" checked>1</option><!-- 不写冒号就是字符串但可以v-model.number -->
<option value="2">2</option> <!-- 不写冒号就是字符串但可以v-model.number -->
<option value="3">3</option> <!-- 不写冒号就是字符串但可以v-model.number -->
</select>
<button @click="JIA(addnum)">+</button>
<button @click="JIAN(addnum)">-</button>
<button @click="oddAdd(addnum)">当前求和为奇数再加</button>
<button @click="waitAdd(addnum)">等1秒再加</button>
<h2 style="color:red">Count里边读personList</h2>
<ul style="color:red">
<li v-for="p in personList" :key="p.id">{{ p.name }}</li>
</ul>
</div>
</template>
<script>
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex';
export default {
name: 'Count',
data() {
return {
addnum: 1
}
},
computed: {
personList() {
return this.$store.state.personAbout.personList;
},
...mapState('countAbout', ['sum', 'school', 'subject']),
...mapGetters('countAbout', { bigSum: 'bigSum' }),
},
methods: {
...mapMutations('countAbout', ['JIA', 'JIAN']),
...mapActions('countAbout', { oddAdd: 'oddAdd', waitAdd: 'waitAdd' }),
},
mounted() {
console.log(this.$store)
}
};
</script>
(4)Person.js
import axios from 'axios';
import { nanoid } from 'nanoid';
export default {
namespaced: true,
state: {
personList: [
{ id: 1, name: 'zzy' }
]
},
getters: {
firstPersonName(state) {
return state.personList[0].name;
}
},
actions: {
addPersonHan(context, value) {
if (value.name.indexOf('韩') === 0) {
context.commit('ADD_PERSON', value);
} else {
alert('添加的人不姓韩!');
}
},
//发送ajax请求拿到名字
addPersonServer(context) {
axios.get('http://api.uixsj.cn/hitokoto/get?type=social').then(
response => {
context.commit('ADD_PERSON', { id: nanoid(), name: response.data });
},
error => {
console.log(error.message);
}
)
}
},
mutations: {
ADD_PERSON(state, value) {
state.personList.unshift(value);
}
}
}
(5)Person.vue
<template>
<div>
<h2>Person里边读personList</h2>
<input type="text" placeholder="请输入名字" v-model="name">
<button @click="addPerson">添加</button>
<button @click="addPersonHan">添加一个姓韩的人</button>
<button @click="addPersonServer">随机添加一个名字</button>
<h2>第一个人的名字:{{ firstPersonName }}</h2>
<ul>
<li v-for="p in personList" :key="p.id">{{ p }}</li>
</ul>
<h2 style="color:red">Person里读sum:{{ add }}</h2>
</div>
</template>
<script>
import { nanoid } from 'nanoid';
export default {
name: 'Person',
data() {
return {
name: ''
}
},
computed: {
personList() {
return this.$store.state.personAbout.personList;
},
add() {
return this.$store.state.countAbout.sum;
},
firstPersonName() {
return this.$store.getters['personAbout/firstPersonName'];
}
},
methods: {
addPerson() {
const personObj = { id: nanoid(), name: this.name };
this.$store.commit('personAbout/ADD_PERSON', personObj);
this.name = ''; //添加完了输入框置空
},
addPersonHan() {
const personObj = { id: nanoid(), name: this.name };
this.$store.dispatch('personAbout/addPersonHan', personObj);
},
addPersonServer() {
this.$store.dispatch('personAbout/addPersonServer');
}
},
};
</script>
注意:只有指定相应的模块名才能找到对应的数据和方法(前提是模块开启了命名空间)