一、Vuex的介绍
1. 概念
专门在Vue中实现集中式状态数据管理的一个Vue插件,对Vue的应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
2. 了解vuex地址
https://github.com/vuejs/vuexhttps://github.com/vuejs/vuex
3. 图像形式理解vuex
当通过全局事件总线或者消息订阅与发布传递信息时候,给多个组件要一一发送,如果修改又得一一进行修改
vuex可以解决一一发送这一弊端
所以vuex主要使用场景就是:
- 当多个组件依赖同一个状态
- 多个不同组件的行为需要变成更同一状态
4. 通过案例进行理解:
纯vue案例:进行计算
<template> <div> <h1>当前求和为:{{sum}}</h1> <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//注意会拼接字符, //是因为没有对option数据进行绑定,绑定之后引号内的就是js表达式 //使用修饰符对数据进行转换成数值 }, decrement(){ this.sum -= this.n }, incrementOdd(){ //此处使用取余计算,如果等于0,转换为布尔值就不会进入判断里面 if(this.sum % 2){ this.sum += this.n } }, incrementWait(){ setTimeout(()=>{ this.sum += this.n },500) }, }, } </script>
- 首先绑定数据
- 布置页面并设置按钮执行事件
- 设置执行事件的逻辑
- 存在问题:绑定数据的时候不是数值
- 利用:进行绑定value值
- 或者直接使用修饰符:number
5. Vue工作原理图
vuex重要组成部分:Actions、Mutations、State
State:对象,能保存很多key-value默认值
组件:组件中存在各种方法,调用api-dispatch(‘动作类型’,动作值)
Actions:object对象,key和上面动作类型对上,然后调用key所对应的函数,然后自己调用commit(提交)
Mutations:object对象,这个对象上面也存在key和上面动作类型对上,然后调用key所对应的函数,函数包含(整个state,动作值)自动进行mutate
然后state就会发生变化,然后重新进行解析
Action,ajax获取到Backend API获取其他服务器上的数据
当Action的值自己知道,那么就直接可以组件执行commit给Mutations
Devtools:直接和mutations对话
简单理解:
组件 === 客人
Action === 服务员
Mutations === 后厨
State === 菜
二、Vuex的使用
1.vuex开发环境搭建
安装vuex:注意vue3成为默认版本,vuex更新到了4版本(在vuex2项目中安装vuex3)
npm i vuex@3
插件的使用:目的是使用vuex之后在vm中存在store配置
main中引入vuex
使用vuex,引入和使用在main文件省略(因为会报错)
然后vue实例中配置store
注意:如果没有引入并使用vuex那么就不会在vm或者vc中产生store配置
就算再vm实例中配置很多自定义匹配项但是vm不认就会丢弃
new Vue({ el:'#app', render: h => h(App), //store:'hello',//没有引入vux之前再组件中无法找到store store,//简写方式:需要在main中引入并使用之后才能配置,然后就会出现在vc和vm中 }, })
配置store文件:看见store就等于看见vuex的服务员、后厨、菜
- src中配置store文件
- 然后在其中创建index.js文件
设置store :
配置以上三个重要组成部分
然后引入vuex并进行实例化(注意是实例化store)并进行暴露
然后将配置放在实例中(简写方式:key值相等可以简写)
//引入vuex import Vuex from 'vuex' //配置actions--用于响应组件中的动作 const actions = {} //准备mutations--用于操作数据 const mutations = {} //准备state--用于存储数据 const state = {} //创建并暴露store export default new Vuex.Store({ actions, mutations, state, })
然后在main文件中引入store
// 引入store // import store from './store/index'后面的文件可以省略(必须是index) import store from './store'
报错:Vue.use(vuex)应该在store实例化之前
所以在main中不进行引入vuex和使用(上面提到的报错)
因为引入store时候会自动在使用vuex之前解析出来
直接在store文件中引入vue、vuex,并进行使用vue.use(vuex)
import Vue from 'vue' //引入vuex import Vuex from 'vuex' // 使用插件在创建store之间进行使用vuex Vue.use(Vuex)
2. vuex写上面计算案例(只用一个组件进行了解vuex工作过程:此处只写加法:进行了解计算方式)减法相同
- 首先将数据放在state中,然后组件中关于sum的计算就不能执行
- 组件中设置方法传给actions
increment(){ // 此处可以不通过actions直接执行mutations获取数据执行方法 this.$store.dispatch('jia',this.n) },
- actions中配置方法:方法中存在两个参数,分别是上下文和传入的值
- 并进行上传上下文中的计算方式
//两个参数:一个是miniStore(context),后面是value jia(context, value) { console.log(context, value); context.commit('JIA',value) },
- 在mutation中进行配置上下下文需要的计算方式
- mutation中的计算方式应该是大写方式(方便观看)
- mutation中方法的参数一个是state,一个是传入的value值
JIA(state,value) { // console.log(state,value);参数分别是数据监测state中的数据和传入的参数 state.sum += value },
- 页面读取效果应该从组件中进行获取,直接获取,不需要进行this获取
- 显示初始值根据vuex中配置进行设置
<h1>当前求和为:{{$store.state.sum}}</h1>
3. 配置奇数加法和等一等计算
- 组件中直接传入vuex计算方式
incrementOdd(){ this.$store.dispatch('jiaOdd',this.n) }, incrementWait(){ this.$store.dispatch('jiaWait',this.n) },
- store文件的actions中配置计算方式:奇数计算和等一等计算
jiaOdd(context, value) { if (context.state.sum % 2) { // 上下文直接提交执行动作 context.commit('JIA', value) } }, jiaWait(context, value) { setTimeout(() => { context.commit('JIAN',value) },500) },
- mutations中的方法还是 上面配置的方法
const mutations = { JIA(state,value) { state.sum += value }, JIAN(state,value) { state.sum -= value } }
4. 优化vuex匹配流程
可以直接通过mutations中获取到的计算方法:在组件中直接使用commit进行获取
increment(){ this.$store.commit('JIA',this.n) }, decrement(){ this.$store.commit('JIAN',this.n) },
上面的JIA和JIAN都在mutations中存在
5. 开发者工具讲解
开发者工具简单理解
- 直接与mutations进行对话,最后的计算都是通过mutations中的计算
开发者工具中每一步存在的操作
- 秒表按钮:直接恢复到指定的计算效果
- 清除效果:直接删除当前的计算效果(删除中间的,所有以来其产生的都会清除)
- 下载按钮:合并当前点击之前的所有计算步骤,并合并
右上角的三个按钮
- 红点:不会在捕获计算步骤
- 清除按钮:清除空所有的操作记录
- 下载:合并所有步骤
右下角的两个按钮
- 导出:将计算过程的最终效果复制(存在在剪切板)
- 导入:直接粘贴在导入框中(Esc退出)
6. vuex中使用的几个问题
为什么需要actions中的context参数
当业务逻辑比较复杂时候,需要先在最外层执行某些事情,然后dispatch某个方法
然后在第二层再处理一点事情,然后调用dispatch
最后一层执行一点事情然后再执行commit
为什么要将简单的计算方式commit给mutations
如果执行奇数计算不用利用commit提交给mutation,可以直接使用state进行计算
context.state.sum += value可以执行但是不规范(开发者工具无法捕获)
为什么将逻辑写在actions而不写在组件方法中
如果执行的动作需要多个服务器进行判断,那么在自身组件写不能实现复用
7.getters配置项,在store中
不是必须存在,如果要将state默认数据进行一些复杂操作就会体现其作用
- 参数就是state中的数据,利用返回值进行计算
// 准备getters用于将state中的数据进行直接加工 const getters = { // state参数就是state bigSum(state) { return state.sum * 10 } }
- 配置并暴露
export default new Vuex.Store({ actions, mutations, state, getters, })
- 模板获取
<h3>当前求和放大10倍数为:{{$store.getters.bigSum}}</h3>
三、优化vuex的使用
以下内容都是在组建中完成
1. mapState和mapGetters
优化获取数据和优化对数据的复杂操作:粗糙方法
直接使用计算属性获取到sum:来自于当前组件的store的state
模板简单了,但是代码还是冗余
采用一种方式可以获取数据并进行自己命名
mapState:映射数据
- 首先进行引入
import {mapState} from 'vuex'
- 计算属性中进行获取(对象和数组方法进行获取)
- 利用剩余参数进行获取,通过开发者工具最后显示的是vuex的绑定
//借助mapstate从state中获取数据 (对象写法):计算属性名字和获取数据名字不同 ...mapState({he:'sum',xuexiao:'school',xueke:'subject'}) //通过数组方式进行获取值(计算属性名字和获取数据名字相同) ...mapState(['sum','school','subject']),
mapGetters
- 首先引入mapGetters
import {mapGetters} from 'vuex'
- 在计算属性中进行获取
computed:{ ...mapGetters({beishu:'bigSum'}),//对象写法 ...mapGetters(['bigSum']),//数组写法 }
2. mapMutations和mapActions
mapMutations
- 首先引入mapMutations
import {mapMutations} from 'vuex'
- 方法中进行获取:注意会出现问题:获取到的是鼠标事件为没有value
- 直接在模板中传递数据
<button @click="increment(n)">+</button> <button @click="decrement(n)">-</button>
...mapMutations({increment:'JIA',decrement:'JIAN'}),//对象写法 ...mapMutations(['JIA','JIAN']),//数组写法(使用数组引入之后的名就是引入的名字)
mapActions
- 首先引入mapActions
import {mapActions} from 'vuex'
- 方法中进行获取:首先利用模板进行传递数据
<button @click="incrementOdd(n)">当前页面为基数+</button> <button @click="incrementWait(n)">等一等再+</button>
...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'}),//对象形式 ...mapActions(['jiaOdd','jiaWait']),//数组形式,注意没有生成名字
以上两个传value值不仅可以通过模板进行获取,还可以通过方法直接进行获取(不使用)
increment(){ this.接收自定义名字(value值) }
四、多组件共享数据
创建新的person组件:配置一个不同页面效果
进行配置组件和vuex
- state数据配置一个对象,在组件中进行遍历获取,计算属性可以直接简化遍历获取源
//store数据编写 personList: [ {id: '001', name:'张三'} ] //组件中遍历名字 <ul> <li v-for="p in personList" :key='p.id'>{{p.name}}</li> </ul> //获取数据 personList(){ // 注意获取数据需要使用return进行返回 return this.$store.state.personList }, // 获取数据 // ...mapState(['personList'])
- 配置按钮点击方法:依赖nanoid生成的新的对象并commit
- 直接在mutations中添加一个方法(给数据添加人)
add(){ const personObj = {id:nanoid(),name:this.name} this.$store.commit('ADD_PERSON',personObj) this.name = '' } //vuex ADD_PERSON(state, value) { state.personList.unshift(value) }
然后通过上面获取数据的简易方法获取到数据中人数就可以在其他组件中使用
五、vuex模块化
vuex中的不同组件使用的vuex数据和方法都不同:多个不同的vuex内容混合在一块容易混乱
1. 区分组件对象(命名)并配置namespaced配置(开启命名空间)
- 进行区分:此处只演示person-vuex模块
- 确定分类:配置项namespaced:true默认值false
- 通过以上配置组件中获取模块化名字才会被认识
const personOptions = { namespaced: true,//此处默认值false actions: { // 如果包含王:判断0==0 为正确则提交使用mution方法 addPersonWang(context, value) { if (value.name.indexOf('王') === 0) { context.commit('ADD_PERSON',value) } else { alert('添加姓王的人') } } }, mutations: { ADD_PERSON(state, value) { state.personList.unshift(value) } }, state: { personList: [ {id: '001', name:'张三'} ] }, }
2. 暴露组件模块并配置自定义名字
- 进行区分之后的store中暴露就不是直接暴露vuex配置:利用modules配置进行区分
- 注意通过模块化之后进行获取就会出现问题:获取不到数据和actions等
- 但是组件模块化之后就存在了自定义名字
//创建并暴露store export default new Vuex.Store({ // 暴露store的配置:上面配置命名之后以下才会认识 modules: { countAbout: countOptions, personAbout:personOptions } })
3. 组件中获取模块化内的数据就需要通过先区分模块再进行引入
- 然后组件中模板直接获取数据:简易方法需要配合上方的自定义名字
...mapState('countAbout',['sum','school','subject','personList']), ...mapState('personAbout',['personList']),
computed:{ ...mapState('countAbout',['sum','school','subject','personList']), ...mapState('personAbout',['personList']), ...mapGetters('countAbout',['bigSum']),//数组写法 }, methods: { ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),//对象写法 ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'}),//对象形式 },
4. 通过原生方法获取vuex中数据和方法并进行添加时候的代码格式
注意这里使用 / 进行分割
//获取vuex中数据 computed:{ personList(){ // 注意获取数据需要使用return进行返回 return this.$store.state.personAbout.personList }, } //对模块化vuex中的数据进行获取并执行mutations的方法时候代码格式 add(){ const personObj = {id:nanoid(),name:this.name} this.$store.commit('personAbout/ADD_PERSON',personObj) this.name = '' },
5.通过原生方法获取模块化中的getters
获取数据的时候需要通过【】获取对象数据方法:包裹组件模块暴露的自定义名字
//上面组件中已经配置添加姓王名字 //组件模块中配置getters-获取第一个人名 getters: { firstPersonName(state) { return state.personList[0].name } },
//组件中使用 computed:{ firstPersonName(){ // 获取对象方法数组获取 return this.$store.getters['personAbout/firstPersonName'] } },
6. 优化模块化代码
创建单独的vuex文件:将vuex配置都放在其中并暴露
创建一个总的vuex:
- 总文件中引入单个的vuex
- 并暴露
//该文件创建vuex最重要的store import Vue from 'vue' //引入vuex import Vuex from 'vuex' // 引入store模块 import countOptions from './count' import personOptions from './person' // 使用插件在创建store之间进行使用vuex Vue.use(Vuex) //创建并暴露store export default new Vuex.Store({ // 暴露store的配置:上面配置命名之后以下才会认识 modules: { countAbout: countOptions, personAbout:personOptions } })
7.服务器获取人名-对应组件中进行编写
此处使用一个免费api:
https://api.uixsj.cn/hitokoto/get?type=socialhttps://api.uixsj.cn/hitokoto/get?type=social
//首先引入nanoid import { nanoid } from "nanoid" //actions addPersonServe(context) { // 发送get请求获取数据 axios.get('https://api.uixsj.cn/hitokoto/get?type=social').then( response => { context.commit('ADD_PERSON',{id:nanoid(),name:response.data}) }, error => { alert(error.message) } ) },
//组件中进行设置方法 <button @click="addPerson">添加一个人,名字随机</button> //进行获取actions中的方法 addPerson(){ this.$store.dispatch('personAbout/addPersonServe') }