Vue3通信方式
# Vue3通信方式
- props
- 自定义事件
- 全局事件总线$bus
- v-model
- useAttrs
- ref与$parent
- provide与inject
- vuex(pinia)
- slot
- pubsub
# props
通过defineProps获取父组件传递的数据
# 自定义事件
一种是原生的DOM事件,另外一种自定义事件
原生DOM事件:click、dbclick、change、mouseenter、mouseleave....
自定义事件:实现子组件给父组件传递数据
在vue3框架click、dbclick、change(这类原生DOM事件),不管是在标签、自定义标签上(组件标签)都是原生DOM事件。
// 父组件
<Event2 @xxx="handler3" @click="handler"></Event2>
// 子组件
let $emit = defineEmits(["xxx",'click']);
2
3
4
5
# 全局事件总线$bus
mitt:官网地址:https://www.npmjs.com/package/mitt
安装
npm install --save mitt
1引入到项目并挂载
可以在
main.js
挂载到全局// 标准的ES模块化引入方式 import mitt from 'mitt' const app = createApp(App) // vue3.x的全局实例,要挂载在config.globalProperties上 app.config.globalProperties.$EventBus = new mitt()
1
2
3
4
5
6
7/common/EventBus.js
:也可以封装一个ES模块,对外暴露一个Mitt实例import mitt from 'mitt' export default new mitt()
1
2
使用
通过on监听/emit触发
/* * App.vue */ // setup中没有this,需要通过getCurrentInstance来获取Vue实例 import { getCurrentInstance } from 'vue' import { Mp3Player } from '/common/Mp3Player.js' export default { setup(){ // ctx等同于Vue2.x的this const { ctx } = getCurrentInstance() // 监听-如果有新任务则播放音效 ctx.$EventBus.on('newTask', data => { Mp3Player.play() }) // 也可以通过*监听所有任务 ctx.$EventBus.on('*', data => { console.log('EventBus come in', data) }) } } /* * Control.vue */ // 判断有新任务时,触发 ctx.$EventBus.emit('newTask', data)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29off移除事件
# v-model
收集表单数据(数据双向绑定),除此之外它也可以实现父子组件数据同步
利用*props[modelValue]与自定义事件[update:modelValue]*实现的
// 父组件
<Child :modelValue="money" @update:modelValue="handler"></Child>
// 子组件内部
<button @click="handleChild"/>
// 接收props
const props = defineProps(['modelValue'])
// 接收自定义事件
const $emit = defineEmits(['update:modelValue'])
const handleChild = () => {
// 调用自定义组件给父组件传递参数
$emit('update:modelValue',props.modelValue + 1000)
}
2
3
4
5
6
7
8
9
10
11
12
13
利用v-model
// 相当于传递一个props(modelValue),绑定一个自定义事件update:modelValue
// 实现父子组件数据同步
<Child v-model="msg"></Child>
2
3
使用多个v-model,让父子组件多个数据同步
// 传递两个props分别是pageNo与pageSize,绑定两个自定义事件update:pageNo与update:pageSize
// 实现父子组件数据同步
<Child v-model:pageNo="msg" v-model:pageSize="msg1"></Child>
2
3
# useAttrs
获取组件的属性与事件(包含:原生DOM事件或者自定义事件)
<my-button type="success" size="small" title='标题' @click="handler"></my-button>
在子组件内部
通过useAttrs方法获取组件属性与事件
<template>
<--可获取全部属性-->
<my-button :="$attrs"></my-button>
</template>
<script setup lang="ts">
import {useAttrs} from 'vue';
let $attrs = useAttrs();
</script>
2
3
4
5
6
7
8
9
警告
如果defineProps接受了某一个属性,useAttrs方法返回的对象身上就没有相应属性与属性值
# ref与$parent
ref:可以获取真实的DOM节点,可以获取到子组件实例VC
$parent:可以在子组件内部获取到父组件的实例
在父组件内部通过ref获取元素的DOM或者获取子组件实例的VC,那么子组件内部的方法与响应式数据父组件可以使用的
# ref
父组件
<div>{{money}}</div>
<button @click="handle">调用子组件内部</button>
<Son ref="son"></Son>
<script>
let money = ref(10000)
let son = ref()
const handle = ()=>{
money.value += 100
// 调用子组件
son.value.moeny -= 100
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
子组件内部
<script>
let money = ref(666)
// 组件内部数据对外关闭,别人不能访问
// 如果想让外部访问需要通过defineExpose方法对外暴露
defineExpose({
money
})
</script>
2
3
4
5
6
7
8
9
# $parent
父组件
<script>
let money = ref(666)
// 组件内部数据对外关闭,别人不能访问
// 如果想让外部访问需要通过defineExpose方法对外暴露
defineExpose({
money
})
</script>
2
3
4
5
6
7
8
9
子组件内部
<template>
<div>{{money}}</div>
<button @click="handle($parent)">点击调用父组件方法</button>
</template>
<script>
let money = ref(666)
const handle = ($parent)=>{
money.value += 10
$parent.money -= 10
}
</script>
2
3
4
5
6
7
8
9
10
11
12
# provide与inject
provide[提供],inject[注入] 实现隔辈组件传递参数
- provide 提供数据(需要传递两个参数,分别提供数据的key与提供数据value)
- inject 获取数据(通过key获取存储的数值)
<script setup lang="ts">
import {provide} from 'vue'
provide('token','admin_token');
</script>
// 后代组件
<script setup lang="ts">
import {inject} from 'vue'
let token = inject('token');
</script>
2
3
4
5
6
7
8
9
10
# pinia
pinia官网:https://pinia.web3doc.top/
核心概念:state、actions、getters
写法:选择器API、组合式API
# 选择式API
# 创建pinia
创建大仓库store/index.ts
import { createPinia } from 'pinia' let store = createPinia() export default store
1
2
3
4在入口文件main.ts
// 引入仓库 import store from './store' app.use(store)
1
2
3
4创建模块式modules
创建小仓库modules/info.ts
import { defineStore } from 'pinia' // 两个参数:第一个参数 仓库名字,第二个参数 小仓库配置对象 // defineStore会返回一个函数,函数作用域让组件可以获取到仓库数据 let useInfoStore = defineStore("info",{ // 存储数据 state:() => { return { count:99, arr:[1,2,3] } }, actions:{ // 函数没有context上下文对象 // 没有commit、没有mutations去修改 updateNum(a:number){ this.count+=a } }, getters:{ total(){ return this.arr.reduce((prev:number,next:number)=>{ return prev + next; },0) } } }) export default useInfoStore;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 使用pinia
<template>
{{infoState.count}}
<--直接获取getters-->
{{infoState.total}}
<button @click="updateCount">
点击修改仓库数据
</button>
</template>
<script setup lang="ts">
import useInfoStore from '../../store/modules/info'
// 获取小仓库对象
let infoState = useInfoStore()
// 修改数据方法
const updateCount = ()=>{
// 方法一
infoState.count++
// 方法二
infoState.$patch({
count:111
})
// 方法三(调用actions中的方法,类似vuex的dispatch)
// 仓库调用自身方法去修改仓库数据
infoState.updateNum(11);
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 组合式API
# 创建pinia
创建小仓库store/modules/todo.ts
import { defineStore } from 'pinia'
import { ref,computed } from 'vue'
let useTodoStore = defineStore('todo',()=>{
let todos = ref([
{
id:1,
title:'吃饭'
},
{
id:2,
title:'睡觉'
},
{
id:3,
title:'打豆豆'
},
])
let arr = ref([1,2,3])
// 计算属性
const total = computed(()=>{
return arr.value.reduce((prev:number,next:number)=>{
return prev+next
},0)
})
// 务必返回一个对象:属性和方法可以提供给组件使用
return {
todos,
arr,
total,
updateTodo(){
todos.value.push({
id:4,
title:'组合式API'
})
}
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 使用pinia
<template>
<p @click="updateTodo">
{{todoStore.todos}}
</p>
<--计算属性-->
<div>
{{todoStore.total}}
</div>
</template>
<script setup lang="ts">
// 引入组合式API函数仓库
import useTodoStore from '../../store/moudules/todo'
let todoStore = useTodoStore()
// 点击P段落修改仓库
const updateTdo = ()=>{
todoStore.updateTodo()
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# slot
插槽:默认插槽、具名插槽、作用域插槽可以实现父子组件通信
# 默认插槽
父组件
<Todo>
<h1>我是默认插槽填充的结构</h1>
</Todo>
2
3
在子组件Todo内部的模板中书写slot全局组件标签
<template>
<div>
我是子组件
<slot></slot>
我是子组件
</div>
</template>
2
3
4
5
6
7
# 具名插槽
带有名字在组件内部留多个指定名字的插槽
父组件
<Todo>
<template v-slot:a>
<div>
我是填充具名插槽a的位置结构
</div>
</template>
<--简写-->
<template #b>
<div>
我是填充具名插槽b的位置结构
</div>
</template>
</Todo>
2
3
4
5
6
7
8
9
10
11
12
13
14
子组件Todo
<template>
<div>
具名插槽a填充数据
<slot name="a"></slot>
具名插槽b填充数据
<slot name="b"></slot>
</div>
</template>
2
3
4
5
6
7
8
9
# 作用域插槽
可以理解为子组件数据由父组件提供,但是子组件内部决定不了自身结构与外观(样式)
子组件
<ul>
<li v-for="(item,index) in todos" :key="item.id">
<slot :$row="item" :$index="index"></slot>
</li>
</ul>
2
3
4
5
父组件
<Test :todos="todos">
<template v-slot="{$row,$index}">
<span :style="{color:$row.done?'green':'red'}">{{$row.title}}</span>
</template>
</Test>
2
3
4
5