1、vuex
在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
优点分析:全局事件总线是在面对较多的组件之间都需要共享数据,即一起读、写数据时会写很多的$bus.$on和$bus.$emit,优势不如vuex简单
1、案例
vuex求和案例
实现我们使用之前学习的vue简单实现需求,代码如下
//App组件代码
<template>
<div>
<Count/>
</div>
</template>
<script>
import Count from './components/Count'
export default {
name:'App',
components:{Count},
}
</script>
//count组件代码
<template>
<div>
<h1>当前求和为:{{sum}}</h1>
<!-- 注意n一开始是数字1,但是选择时的值下面都是“2”这种字符串,会导致变成字符串运算 -->
<!-- 要么变成:value="",使得其值未js对象当中数字1;要么v-model.number强制类型转化 -->
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementOdd">当前求和为奇数再加</button>
<button @click="incrementWait">等一等再加</button>
</div>
</template>
<script>
export default {
name:'Count',
data() {
return {
n:1, //用户选择的数字
sum:0 //当前的和
}
},
methods: {
increment(){
this.sum += this.n
},
decrement(){
this.sum -= this.n
},
incrementOdd(){
if(this.sum % 2){
this.sum += this.n
}
},
incrementWait(){
setTimeout(()=>{
this.sum += this.n
},500)
},
},
}
</script>
<style lang="css">
button{
margin-left: 5px;
}
</style>
2、Vuex工作原理
不难发现,上面的图中,(‘jia’,2)感觉没有经过Actions的必要,完全可以直接传到Mutations进行计算。
1、Actions作用:当1只有动作类型'jia'时,具体数据需要向服务器获取,此时Actions复制向服务器发送ajax请求获取数据
2、不需要向服务器获取获取数据时Vue Components可以直接commit('jia',2)到Mutations,
只是图中没有体现,是允许直接commit,跳过Actions
可以理解为客人=组件、服务员=Actions、厨师=Motations、菜=States
客人向服务员点菜,服务员转信息给厨师,厨师做菜,再给客人
客人可以直接取后厨找厨师点菜,跳过服务员
换菜单了,客人没法点菜,或者菜单中一些菜不够,或者矛盾(即数据需要处理)需要服务员处理才行
3、搭配vuex环境
1、安装vuex:npm i vuex(默认是安装的vuex4,需要vue3的版本)
显示所有版本号:npm view vuex versions
指定安装版本号:npm install vuex@3.6.2 --save
vue2使用 vuex3
vue3使用 vuex4
2、main.js当中使用插件:vue.use(Vuex)
3、完成store来管理其中的三个对象
4、让所有的vc都可以看见store
vue官方推荐新建src/store/index.js来创建store
//src/store/index.js
//该文件用于创建Vuex中最为核心的store
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)
//准备actions——用于响应组件中的动作
const actions = {}
//准备mutations——用于操作数据(state)
const mutations = {}
//准备state——用于存储数据
const state = {}
//创建并暴露store。暴露即导出,即其他文件可以导入该js文件
export default new Vuex.Store({
//actions:actions,的简写,第一个actions是store固定内容,第二个是上面定义的数据,
//es6中,对象的key和value相同就可以简写
actions,
mutations,
state,
})
然后在main.js内引入store,下面是部分代码
//引入store,没说引入store文件夹的哪一个文件默认导入index.js
import store from './store'
//创建vm,引用store,这样在App和Count组件都可以访问store
new Vue({
...
store
})
Tips:此次我们引入vue、vuex都是在创建stores的index.js里面。
1、vue中引入插件时会先执行完插件
2、代码中存在引入的import代码,无论其在代码何处,其执行优先级最高,优先执行
此处创建store实例前必须先引入vue和vuex,并且use使用
但是如果和之前一样在main.js内引入的话:
import Vue from 'vue'
import Vuex from 'vuex'
import store from './store'
Vue.use(Vuex)
无论import在哪里,引入store.js都会在Vue.use(Vuex)之前执行,但是store.js当中创建
Vuex实例new Vuex.Store({})都需要先use(Vuex)才能行。所以说上面都是把引入和使用的代码放在index.js内部。
上面提到了App和Count组件都可以访问到store,即可以正常访问上面原理图中store内的Api
尝试输出上面的结果发现:现在就ok了
4、案例vuex版本
现在我们数据还在Count组件内,使用vuex我们必须把数据放到store下的state当中
vuex共享数据,也就是说至少有两个组件都向vuex读写数据才交共享,上面App内只有一个组件,没法完美显示vuex优点(因为数据sum放在Count和vuex没区别,必须要多个共享sum),不过此时我们先就让Count一个组件可以正常读写vuex内的data即可,第二个组件后面加
1、把数据sum放入state当中,之前在Count中
所以说Count当中html代码要改,并且methods内运算函数也要改,都是因为无法向之前一样访问sum数据了
2、然后就是点击事件和数据渲染都是在Count组件内实现,跳过点击事件和$store.
首先是Count组件代码,和vue版本相比只修改了js代码,其中的JIA和JIAN这些来自下面的store
<script>
export default {
name:'myCount',
data() {
return {
n:1, //用户选择的数字
}
},
methods: {
//注意前两个加法和减法,因为没有任何的逻辑处理之类的
//所以说可以跳过action,直接commit到Mutation,但是注意Mutation当中是大写的JIA和JIAN
increment(){
this.$store.commit('JIA',this.n)
},
decrement(){
this.$store.commit('JIAN',this.n)
},
incrementOdd(){
this.$store.dispatch('jiaOdd',this.n)
},
incrementWait(){
this.$store.dispatch('jiaWait',this.n)
}
},
mounted() {
console.log('myCount',this)
},
}
</script>
store注意事项:
1、actions即服务员,可以处理业务逻辑,如下面的定时器和判断奇偶
mutations即厨师,里面只能直接加工数据,即直接加减乘除
2、actions内补虚药处理业务逻辑的可以直接跳过到mutations内,但是mutations内也要写上相应的加工方法。注意和上面的Count组件联系起来,加减法是直接通过commit传到Mutations内,并且加减的函数是按照mutations内的JIA和JIAN为准
2、注意sctions和mutations的第一个参数context和state
//该文件用于创建Vuex中最为核心的store
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)
//准备actions——用于响应组件中的动作
const actions = {
//注意:不难发现,jia和jian没有意义,属于上面原理图中可以跳过的部分,所以说可以注释掉
// jia:function(context,value){ 下面是简写,参数context当中有commit、dispatch、state(数据)
// jia(context,value){
// context.commit('JIA',value)
// },
// jian(context,value){
// context.commit('JIAN',value)
// },
jiaOdd(context,value){
if(context.state.sum % 2){
context.commit('JIA',value)
}
},
jiaWait(context,value){
setTimeout(() => {
context.commit('JIA',value)
}, 500)
},
}
//准备mutations——用于操作数据(state),此处的是习惯大写的JIA作为区分,所以说上面commit内传的时候也是JIA
//JIA()中的两个参数state和value分别是sum和传入的参数value的值,Count组件渲染的sum就是来自于这里
const mutations = {
JIA(state,value){
state.sum += value
},
JIAN(state,value){
state.sum -= value
}
}
//准备state——用于存储数据
const state = {
sum:0 //当前的和
}
//创建并暴露store。暴露即导出,即其他文件可以导入该js文件
export default new Vuex.Store({
//actions:actions,的简写,第一个actions是store固定内容,第二个是上面定义的数据,
//es6中,对象的key和value相同就可以简写
actions,
mutations,
state,
})
5、vuex开发工具和部分细节
1、开发者工具,以Edge浏览器为例:
2、context含义
我们尝试在、‘
、index.js的Action部分输出context,观察其作用
jiaOdd(context,value){
console.log('actions中的jiaOdd被调用了',context)
...
}
dispatch函数是Vuex中一个被广泛使用的函数,它用于触发actions中的函数。
diapatch派遣;派出;发出
那么为什么Action当中的dispatch是来触发它自身的函数的呢?
当需要进行的处理逻辑复杂,或者需要多个函数处理时,就可以在Action内的函数间使用
3、既然action的context有sum,为什么不直接在action里面加减
4、为什么业务逻辑代码都是在action当中,明明Count组件内也可以
也就是直接写在Count组件的methods内,但是要考虑到该案例中逻辑处理简单还好说,复杂了的话就代码冗余。外加如果逻辑处理代码需要被多次使用,这样代码复用率低
6、store之getters配置项
以上面例子为例,我们需要把最后的sum再进行运算然后显示在页面当中
(1)、直接写运算代码
<h1>当前求和为:{{$store.state.sum}}</h1>
<h1>当前求和为:{{$store.state.sum * 10}}</h1>
(2)、计算属性
<h1>当前求和的十倍为:{{ bigSum }}</h1>
<script>
...
computed:{
bigSum(){
return $store.state.sum * 10
}
}
</script>
(3)、getters配置项
运算简单的时候使用上面两个都无所谓,但是当运算复杂,且需要复用的时候便不推荐,getters是最合适的。
//Count组件内
<h1>当前求和放大十倍为:{{$store.getters.bigSum}}</h1>
//index.js代码内。getters是和action、motation同一级的
//准备getters——用于将state中的数据进行加工
const getters = {
bigSum(state){
return state.sum*10
}
}
不难发现:虽然都是对数据进行一定的处理
计算属性只对其所属部分的组件代码有效
getters配置项是全局有效
7、四个map方法的使用
不难发现,上面的Count组件代码可以优化:
我们可以考虑在html代码中只写sum和subject,school,然后在计算属性中返回完整值
computed:{
//靠程序员自己亲自去写计算属性
sum(){
return this.$store.state.sum
},
school(){
return this.$store.state.school
},
subject(){
return this.$store.state.subject
},
bigSum(){
return this.$store.getters.bigSum
},
}
但是我们又发现计算属性中sum、school、subject又是高度相似都来自于state,bigSum来自getters。还可以优化
1、mapState方法:用于帮助我们映射state
中的数据为计算属性
computed: {
//借助mapState生成计算属性:sum、school、subject(对象写法)
...mapState({sum:'sum',school:'school',subject:'subject'}),
//借助mapState生成计算属性:sum、school、subject(数组写法)
...mapState(['sum','school','subject']),
},
2、mapGetters方法:用于帮助我们映射getters
中的数据为计算属性
computed: {
//借助mapGetters生成计算属性:bigSum(对象写法)
...mapGetters({bigSum:'bigSum'}),//借助mapGetters生成计算属性:bigSum(数组写法)
...mapGetters(['bigSum'])
},
所以说最后的代码如下:mapstate因为是对象,computed也是对象,所以说前面要加上...表示把sum、school、subject依次取出放入mapState
<h1>当前求和为:{{sum}}</h1>
<h3>当前求和放大10倍为:{{bigSum}}</h3>
<h3>我在{{school}},学习{{subject}}</h3>
computed:{
//借助mapState生成计算属性,从state中读取数据。(对象写法)
//下面第一个sum对应上面html中的sum,第二个sum是要去找state里面的sum。不能简写,两个虽然相同,但是都是字符不是变量(两个可以不同,即上面html当中和state当中不一样)
...mapState({sum:'sum',school:'school',subject:'subject'}),
//借助mapState生成计算属性,从state中读取数据。(数组写法)
//这才是上面对象的真正的简写方式(只有相同才能简写),记住即可
...mapState(['sum','school','subject']),
//借助mapGetters生成计算属性,从getters中读取数据。(对象写法)
...mapGetters({bigSum:'bigSum'})
//借助mapGetters生成计算属性,从getters中读取数据。(数组写法)
...mapGetters(['bigSum'])
}
然后是Count组件内的methods内的函数写法可以优化,和上面基本相似
对象写法:
注意此时因为运算是由参数n传递到mutation或者action当中的,所以说html当中点击事件不需要加上参数,mapMutations和mapActions会自动传过去
<template>
<div>
<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 {
...
methods: {
//借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法)
...mapMutations({increment:'JIA',decrement:'JIAN'}),
//借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(对象写法)
...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
}
}
</script>
数组写法:
在mapState和mapGetters当中可以知道,数组写法是两个相同才能写。但是上面却是:
increment:'JIA',decrement:'JIAN'
所以说要使用数组写法,只有先变成一样,所以说要修改html当中点击事件的名字
<template>
<div>
<button @click="JIA(n)">+</button>
<button @click="JIAN(n)">-</button>
<button @click="jiaOdd(n)">当前求和为奇数再加</button>
<button @click="jiaWait(n)">等一等再加</button>
</div>
</template>
<script>
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
export default {
...
methods: {
//借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(数组写法)
...mapMutations(['JIA','JIAN']),
//借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(对象写法)
...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
}
}
</script>
-
mapActions方法:用于帮助我们生成与
actions
对话的方法,即:包含$store.dispatch(xxx)
的函数methods:{ //靠mapActions生成:incrementOdd、incrementWait(对象形式) ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'}) //靠mapActions生成:incrementOdd、incrementWait(数组形式) ...mapActions(['jiaOdd','jiaWait']) }
-
mapMutations方法:用于帮助我们生成与
mutations
对话的方法,即:包含$store.commit(xxx)
的函数methods:{ //靠mapActions生成:increment、decrement(对象形式) ...mapMutations({increment:'JIA',decrement:'JIAN'}), //靠mapMutations生成:JIA、JIAN(对象形式) ...mapMutations(['JIA','JIAN']), }
备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象
8、多组件共享
目标
之前就说到,共享讲的是多个组件读写同一个数据,所以说上面只有一个组件的案例还不够全面
再写一个Person组件,展示sum和persons(人员信息)。同理在Count当中也展示sum和persons
重点就是在Person和Count两个组件都可以访问store内的数据:人员信息personList和求和sum两个值。
为此我们在两个组件内都this.$store.state(计算属性内)或者$store.state(div中)即可正常访问数据
unshift() 方法可向数组的开头添加一个或更多元素,并返回新的长度。
首先需要在index.js当中配置:
myPerson组件代码如下所示:
<template>
<div>
//为了简洁,下面的sum和person都是写在计算属性内的
//注意这里就可以直接访问sum和personList
<h1>人员列表</h1>
<h3 style="color: red;">Count组件求和为{{ sum }}</h3>
<input type="text" placeholder="请输入名字" v-model="name">
<button @click="add">添加</button>
<ul>
<li v-for="p in personList" :key="p.id">{{p.name}}</li>
</ul>
</div>
</template>
<script>
//nanoid生成唯一的一串字符编码,用于下面用户信息的id
import {nanoid} from 'nanoid'
export default {
name:'myPerson',
data() {
return {
name:''
}
},
//计算属性内没什么重复的,所以说没有像之前和下面Count一样简写
computed:{
personList(){
return this.$store.state.personList
},
sum(){
return this.$store.state.sum
}
},
methods: {
add(){
const personObj = {id:nanoid(),name:this.name}
//console.log(personObj); 输出personObj对象,包括随机的id和输入框的名字
//因为没有任何的逻辑处理,所以说数据直接commit到mutation当中
this.$store.commit('ADD_PERSON',personObj)
//完事后清空
this.name = ''
}
}
}
</script>
Count组件就简单修改即可:
<h3 style="color:red">Person组件的总人数是:{{personList.length}}</h3>
<script>
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
export default {
...
computed:{
//借助mapState生成计算属性,从state中读取数据。(数组写法)
...mapState(['sum','school','subject','personList']),
//借助mapGetters生成计算属性,从getters中读取数据。(数组写法)
...mapGetters(['bigSum'])
},
methods: {
...
}
</script>