052_组件通信-方式1-props
| 组件关系 | 传递方式 |
|---|---|
| 父传子 | 1, props |
| 2, v-model | |
| 3, $refs | |
| 4, 默认插槽, 具名插槽 | |
| 子传父 | 1,props |
| 2, 自定义事件 | |
| 3,v-model | |
| 4, $parent | |
| 5, 作用域插槽 | |
| 祖传孙,孙传祖 | 1,$attrs |
| 2, provide,inject | |
| 兄弟间, 任意组件间 | 1, mitt |
| 2, pinia |

笔记

6.1 [props]
概述: props 是使用频率最高的一种通信方式,常用与: 父<-->子。
1, 若父传子: 属性值是非函数。
2, 若子传父: 属性值是函数, 你先给我函数,我去调用
笔记


A组件将订单传给B组件,此时你得用组件通信,如果你组件通信玩得不溜,你会非常的痛苦,这两个组件可能是父子关系,兄弟关系,祖孙关系,还是其他关系,所以如果你的组件通信不熟悉,写功能的时候你会非常痛苦,知道这样做,但是代码写不出来,所以从今天开始,我们将vue3里面常见的组件通信都给大家窜一窜,接下来做准备工作

此时我们要将A组件订单传给B组件,我们就得用组件通信,就是要实现组件之间互相通信,你给我什么,我返回给你是啥

接下来我们做准备工作,将public,src,index.html全部删除



红色部分是标题 蓝色部分是导航区

粉色部分为展示区

比如我们想学习props,我们点击props,是专门学习父子组件,如下图所示

自定义组件

mitt

v-model

$attrs

refs,parent

previde,inject

pinia

slot

准备了一个第三方 bootstrap.css样式库

将 bootstrap.css 引入 index.html

主要是src里面,给大家搭建了一个简单的路由小环境

pages 里面你能看到的是如下

router

src/main.ts

App.vue


props既可以实现父传子,也可以实现子传父


src/pages/01_props/Child.vue
<template>
<div class="child">
<h3>子组件</h3>
</div>
</template>
<script setup lang="ts" name="Props">
</script>
<style scoped>
.child {
background-color: skyblue;
padding: 10px;
box-shadow: 0 0 10px black;
border-radius: 10px;
}
</style>

src/pages/01_props/Father.vue
<template>
<div class="father">
<h3>父组件</h3>
<Child />
</div>
</template>
<script setup lang="ts" name="Props">
import Child from '@/pages/01_props/Child.vue'
</script>
<style scoped>
.father {
back
}
</style>




父给子能传递数字,父给子也能传递函数,还能传递数组,也能传递对象



1,父传子
父组件传递数据

子组件接收数据

效果

2, 子传父
孩子把玩具给父亲玩一会儿

接收数据发父亲需要提前定义好一个方法






收到 'sendToy' 在模板里面就可以直接使用,在使用时牢记要把toy传过去


父直接传给子,要实现子传父,先父先传一个函数给子,子在以传参的形式传给父

使用props要实现子传父,父必须先给子传函数

父如何将收到的玩具呈现到页面上 代码-效果


使用 v-show或者v-if 实现条件渲染 代码-效果


当点击'把玩具给父亲'的按钮时,页面如下

绿色框里面属于非函数

这种不要用props传递数据,一层一层太麻烦

虽然可以实现父传子,子再传孙

虽然使用props能实现,不过太闹腾了

bootstrap 官网
https://v5.bootcss.com/docs/getting-started/introduction/
_组件通信-方式1-props 实现代码如下:
1, src/pages/01_props/Child.vue
<template>
<div class="child">
<h3>子组件</h3>
<h4>玩具: {{ toy }}</h4>
<h4>父给的汽车: {{ car }}</h4>
<button @click="sendToy(toy)">把玩具给父亲</button>
</div>
</template>
<script setup lang="ts" name="Props">
import { ref } from 'vue'
// 数据
let toy = ref('奥特曼')
// 声明接收props
let props = defineProps(['car','sendToy'])
</script>
<style scoped>
.child {
background-color: skyblue;
padding: 10px;
box-shadow: 0 0 10px black;
border-radius: 10px;
}
</style>
2, src/pages/01_props/Father.vue
<template>
<div class="father">
<h3>父组件</h3>
<h4>汽车: {{ car }}</h4>
<h4 v-show="toy">子给的玩具: {{ toy }}</h4>
<Child :car="car" :sendToy="getToy" />
</div>
</template>
<script setup lang="ts" name="Props">
import Child from '@/pages/01_props/Child.vue'
import { ref } from 'vue'
// 数据 父组件向子组件传递数据
let car = ref('奔驰')
let toy = ref('')
// 方法
function getToy(value: string) {
// 修改数据
toy.value = value
// console.log('父',value)
}
</script>
<style scoped>
.father {
background-color: rgb(165,164,164);
padding: 20px;
border-radius: 10px;
}
</style>

053_组件通信-方式2-自定义事件
src/pages/02_custom_event/Father.vue
<template>
<div class="father">
<h3>父组件</h3>
<Child />
</div>
</template>
<script setup lang="ts" name="Father.vue">
import Child from '@/pages/02_custom-event/Child.vue';
</script>
<style scoped>
.father {
background-color: rgb(165,164,164);
padding: 20px;
border-radius: 10px;
}
.father button {
margin-right: 5px;
}
</style>

src/pages/02_custom_event/Child.vue
<template>
<div class="child">
<h3>子组件</h3>
</div>
</template>
<script setup lang="ts" name="Child">
</script>
<style scoped>
.child {
background-color: rgb(76,200,76);
padding: 20px;
box-shadow: 0 0 10px black;
border-radius: 10px;
}
</style>

指针事件:DOM操作的基本功


$event 占位符,事件对象,通过事件对象,可以获取屏幕的宽度,鼠标的位置,时间戳等指针事件参数,实现代码如下,指针事件如上图所示
<template>
<div class="father">
<h3>父组件</h3>
<button @click="test(6,7,$event)">点我</button>
<Child />
</div>
</template>
<script setup lang="ts" name="Father.vue">
import Child from '@/pages/02_custom-event/Child.vue';
function test(a:number, b:number,c:Event) {
console.log('父组件被点击了', a, b,c);
}
</script>
代码-效果


点击按钮'点我',屏幕上str的值发生了改变

ref定义的响应式数据,在模板里面使用可以不写 .value

$event是事件对象,而str是字符串

也可以实现点击,返回如下内容

以上就是event事件对象的简单介绍 下面正式使用event

接下来要实现的效果是孩子要把自己的玩具交给父亲

自定义事件是典型的子传父

第四行: 当你对button进行点击事件时,qwe是不是就得调用。
第五行: abc就是事件名,这是自定义事件,xyz就是事件的回调

绿色的第四行表示: 你给组件Child绑定了一个haha事件

当触发haha事件,xyz就会调用,这就是自定义事件

接下来是你如何去触发haha事件

如果写的是 @click ,我们只需要点击就可以触发事件

如果是@keyup 按键弹起,就可以触发键盘事件

如果现在是haha,该如何处理?

第四行是给Child帮了一个事件

Child组件你需要去声明

不是你在父组件帮了一个事件,你就万事大吉,你得在子组件声明

规范是emit可以在模板中使用

你只要写 emit('haha') 就可以触发自定义事件


粉色框里面的代码,你可以在任何你喜欢的地方写上他就会触发这个自定义事件

等组件挂载结束3秒以后在触发haha事件

等组件Child挂载3秒之后我去触发haha事件

代码-效果


只要一触发

他就调用回调函数xyz(),然后就打印


一触发'haha'事件,立马就去调用'xyz'回调函数,然后打印结果xyz


1, 在生命周期函数中使用 emit() 代码-效果
// 等待组件挂载完成3秒之后,触发事件haha,传递玩具的名字
onMounted(()=>{
setTimeout(()=>{
emit('haha')
},3000);
})


2, 在模板中使用 emit() 代码-效果


点击测试 他就触发回调函数xyz(), 效果如下:

他不仅能触发调用回调函数,还能传递数据,代码-效果

你给Child子组件绑定了一个 haha 事件,haha事件只要被触发,就调用红色的'xyz()'函数,


接下来我们就去触发,当你点击按钮'测试'的时候,@click就执行,只要一执行,触发的是emit('haha',666),代理的是666,666以参数的形式自动注入到xyz(value:number)里面来了

测试 代码-效果


接下来,我们能不能写得再高端一点呢?你给Child组件帮定一个@send-toy发送玩具,完毕就去调用saveToy保存玩具的回调函数

完整代码-效果


打印是在父组件中完成的,此时你已经成功的实现了子传父

代码-效果

刷新的效果

点击 '测试'后的效果


自此,使用emit实现了子传父 自定义事件
send-toy 是肉串命名 这是官方推荐写法

saveToy小驼峰命名 这是官方推荐写法

因此,推荐你始终使用 kebab-case的事件名 即短横线隔开命名规范


_组件通信-方式2-自定义事件 实现代码如下
1, src/pages/02_custom-event/Father.vue
<template>
<div class="father">
<h3>父组件</h3>
<h4 v-show="toy">子给父的玩具: {{ toy }}</h4>
<!-- 给子组件Child绑定事件 -->
<Child @send-toy="saveToy" />
</div>
</template>
<script setup lang="ts" name="Father.vue">
import Child from '@/pages/02_custom-event/Child.vue';
import { ref } from 'vue';
// 数据
let toy = ref('');
// 用于保存传递过来的玩具名称的函数或者方法
function saveToy(value: string) {
console.log('saveToy', value);
toy.value = value;
}
</script>
<style scoped>
.father {
background-color: rgb(165,164,164);
padding: 20px;
border-radius: 10px;
}
.father button {
margin-right: 5px;
}
</style>
2, src/pages/02_custom-event/Child.vue
<template>
<div class="child">
<h3>子组件</h3>
<h4>玩具: {{ toy }}</h4>
<button @click="emit('send-toy',toy)">测试</button>
</div>
</template>
<script setup lang="ts" name="Child">
import { ref,onMounted } from 'vue'
// 数据
let toy = ref('奥特曼')
// 声明事件 defineEmits(['toyChanget'])
const emit = defineEmits(['send-toy'])
// 1, 在生命周期函数中使用 等待组件挂载完成3秒之后,触发事件haha,传递玩具的名字
// onMounted(()=>{
// setTimeout(()=>{
// emit('haha')
// },3000);
// })
</script>
<style scoped>
.child {
margin-top: 20px;
background-color: rgb(76,200,76);
padding: 20px;
box-shadow: 0 0 10px black;
border-radius: 10px;
}
</style>
刷新-效果

点击 '测试'-效果

054_组件通信-方式3-mitt
A组件找到粉色的公共组件,我要给你绑定一个事件abc,事件只要被触发就调用xyz,
B组件也去找到公共组件,恰巧就触发他身上的abc事件,也带一个666过去,他只要一触发abc事件,A组件的abc事件就调用了xyz,不仅被调用,而且还能接收参数666



1, pubsub
2, $bus
3, mitt
这三个都是一个套路,
接收数据的: 提前绑定好事件(pubsub提前订阅消息)
提供数据的: 在合适的时候触发事件(pubsub发布消息)
如何使用mitt
mitt 是一个小巧、快速的发布/订阅事件库。它允许你在不同的组件或模块之间进行通信, 而不需要直接引用它们。这对于解耦和保持代码的清晰性非常有帮助。
1, 安装 mitt
npm i mitt
2, 引入mitt
import mitt from 'mitt'
3, 调用并且暴露出去
// 调用mitt得到emitter,emitter可以绑定事件,触发事件 创建一个mitt实例
const emitter = mitt();
// 暴露emitter实例,以便在其他文件中使用
export default emitter;
// export default mitt()
在 src/main.ts中引入 emitter

1, emitter.all 拿到所有绑定事件
2, emitter.emit 触发某一个事件
3, emitter.off 解绑某一个事件
4, emitter.on 绑定某一个事件

mitt官网
https://github.com/developit/mitt
src/pages/mitt/Child1.vue
<template>
<div class="child1">
<h3>子组件1</h3>
</div>
</template>
<script setup lang="ts" name="Child1">
</script>
<style scoped>
.child1 {
margin-top: 50px;
background-color: skyblue;
padding: 10px;
box-shadow: 0 0 10px black;
border-radius: 10px;
}
.child1 button {
margin-right: 10px;
}
</style>

src/pages/mitt/Child2.vue
<template>
<div class="child2">
<h3>子组件2</h3>
</div>
</template>
<script setup lang="ts" name="Child2">
</script>
<style scoped>
.child2 {
margin-top: 50px;
background-color: orange;
padding: 10px;
box-shadow: 0 0 10px black;
border-radius: 10px;
}
</style>

src/pages/mitt/Father.vue
<template>
<div class="father">
<h3>父组件</h3>
<Child1 />
<Child2 />
</div>
</template>
<script setup lang="ts" name="Father">
import Child1 from '@/pages/03_mitt/Child1.vue';
import Child2 from '@/pages/03_mitt/Child2.vue';
</script>
<style scoped>
.father {
background-color: rgb(165,164,164);
padding: 20px;
border-radius: 10px;
}
.father button {
margin-left: 5px;
}
</style>


子组件1要把玩具交给子组件2,如何来实现

子组件1在提供数据 子组件2在接收数据

接收数据---->绑定事件(发布消息)--->emitter.on()
提供数据----->触发事件(订阅消息)--->emitter.emit()

子组件1把玩具给子组件2 完整代码-效果

刷新

点击 '玩具给子组件2' 按钮

不传666, 传toy参数 代码-效果


如何将组件1传递给组件2的数据展示在页面上



完整代码 - 效果

刷新

点击 '玩具给子组件2' 按钮

A组件给B组件传递数据
A组件提供数据: A组件摸到emitter,触发事件[emitter.emit()]
B组件接收数据: B组件也摸到emitter,绑定事件[emitter.on()]

按照以上逻辑,mitt可以实现任意两个组件之间通信的需求
接收数据方 组件卸载时,解绑 send-toy 绑定事件

当组件卸载后,如果不解绑send-toy绑定事件,组件已经不存在, 但是他绑定的事件还存活在,这样对内存有消耗,不友好,所以在组件卸载时,一定要解绑send-toy绑定事件,牢记!牢记!

_组件通信-方式3-mitt 实现代码如下:
1, src/pages/03_mitt/Father.vue
<template>
<div class="father">
<h3>父组件</h3>
<Child1 />
<Child2 />
</div>
</template>
<script setup lang="ts" name="Father">
import Child1 from '@/pages/03_mitt/Child1.vue';
import Child2 from '@/pages/03_mitt/Child2.vue';
</script>
<style scoped>
.father {
background-color: rgb(165,164,164);
padding: 20px;
border-radius: 10px;
}
.father button {
margin-left: 5px;
}
</style>
2, src/pages/03_mitt/Child1.vue
<template>
<div class="child1">
<h3>子组件1</h3>
<h4>玩具: {{ toy }}</h4>
<button @click="emitter.emit('send-toy',toy)">玩具给子组件2</button>
</div>
</template>
<script setup lang="ts" name="Child1">
import { ref } from 'vue'
import emitter from '@/utils/emitter';
let toy = ref('奥特曼')
</script>
<style scoped>
.child1 {
margin-top: 50px;
background-color: skyblue;
padding: 10px;
box-shadow: 0 0 10px black;
border-radius: 10px;
}
.child1 button {
margin-right: 10px;
}
</style>
3, src/pages/03_mitt/Child2.vue
<template>
<div class="child2">
<h3>子组件2</h3>
<h4>电脑: {{ computer }}</h4>
<h4 v-show="toy">收到玩具: {{ toy }}</h4>
</div>
</template>
<script setup lang="ts" name="Child2">
import { ref,onUnmounted } from 'vue'
import emitter from '@/utils/emitter';
let computer = ref('联想')
let toy = ref('')
// 给emitter绑定send-toy事件,接收玩具信息
emitter.on('send-toy',(value:any)=>{
// 接收玩具信息
toy.value = value
// console.log('send-toy',value)
})
// 接收数据方 组件卸载时,解绑 send-toy 绑定事件
onUnmounted(()=>{
emitter.off('send-toy')
})
</script>
<style scoped>
.child2 {
margin-top: 50px;
background-color: orange;
padding: 10px;
box-shadow: 0 0 10px black;
border-radius: 10px;
}
</style>
刷新后

点击 '玩具给子组件2' 按钮后

055_组件通信-方式4-v-model
在项目实际开发中,很少使用 v-model来做通信,
但是UI组件库大量使用 v-model进行通信

Button是组件 a的值是传给组件的

我们需要知道UI组件库 UI组件库大量使用 v-model进行通信,将v-model掌握好,我们将来就可以看懂UI组件库源代码。

准备两个组件,用于实现v-model自定义组件的双向绑定功能
src/pages/04_v-model/Father.vue
<template>
<div class="father">
<h3>父组件</h3>
<input type="text" v-model="username">
</div>
</template>
<script setup lang="ts" name="Father">
import { ref } from 'vue'
// 数据
let username = ref('zhangsan')
</script>
<style scoped>
.father {
padding: 20px;
background-color: rgb(165,164,164);
border-radius: 10px;
}
</style>

v-model 是双向绑定


动态绑定的 :value="username" 用于实现数据到页面

可以实现数据到页面 可以修改红色框里面username的值,页面上会呈现修改后的值。

经测试,页面的数据的改变是不可能来到 蓝色方框里面的

绿色绑定实现双向绑定的一条线,红色绑定实现了双向绑定的另一条线

上图就是v-model实现双向绑定的底层原理:
一个动态的value值,配合input事件
红色<HTMLInputElement>就是断言,表示HTML里面的输入性元素,$event是事件对象,target发生事件的本体 .value 拿到他的值

v-model实现双向绑定代码如下
<input type="text" v-model="username">
v-model实现双向绑定的底层代码如下
<input type="text" :value="username" @input="username=(<HTMLInputElement>$event.target).value">
代码解释
<HTMLInputElement>就是断言,表示HTML里面的输入性元素,$event是事件对象,target发生事件的本体 .value 拿到他的值
绿色的可以实现双向绑定,但是组件 AtguiguInput 虽然可以实现输入,但是他不可以使用v-model实现双向绑定

父组件 与 子组件

Element-UI给我们提供的el-input组件是可以使用v-model实现双向绑定

el-input组件给我们提供的是一个非常漂亮的输入框,并且可以使用v-mode实现双向绑定

Element-UI给el-input身上提供了v-model双向绑定且输入框很漂亮

绿色这条线: 实现数据到页面 :modelValue="username"

vue3的v-model实现双向绑定的写法 update:modelValue 是事件名
<AtguiguInput :modelValue="username" @update:modelValue="username = $event" />
绿色部分为红色部分的双向绑定的一条线从数据到页面,黄色部分为红色部分实现双向绑定的另一条线从页面到数据

你只要写第9行,你就是给组件AtguiguInput
传递了:modelValue="username",
绑定了事件名为 update:modelValue 的事件

绿色为传统输入框



第9行的本质就是红色框里面的数据

绿色的为UI底层库做的

红色的是程序员写的

谁是UI组件库谁操心底层的写法

UI组件库的作者就得写粉色框里面的代码

你们公司封装UI组件库也是这么写的

你为公司封装了一套UI组件库,你的基本功绝对上上层

event 如果是传统的DOM事件对象,你就得event.target.value拿到input元素

父组件里面的event是你传递过来的数据,此时就不需要event.target.value

总结:
$event到底是啥?啥时候能.target
对于原生事件,$event就是事件对象====>能 .target
对于自定义事件,$event就是触发事件时,所传递的数据==>不能 .target

v-model 即能父传子,也能子传父

红色框 父传子 粉色框 子传父

vue2
<input type="text" v-model="username">
v-model 底层实现原理
<input type="text" :value="username" @input="username=(<HTMLInputElement>$event.target).value">
以上两个效果一样

vue3
<AtguiguInput v-model="username" />
<AtguiguInput :modelValue="username" @update:modelValue="username = $event"
<input type="text" :value="modelValue" @input="emit('update:modelValue',(<HTMLInputElement>$event.target).value)">

_组件通信-方式4-v-model(自定义组件实现v-model双向绑定功能),实现代码如下:
1, src/pages/04_v-model/Father.vue
<template>
<div class="father">
<h3>父组件</h3>
<!-- v-model用在html标签上时,默认等同于v-bind:value + v-on:input -->
<!-- <input type="text" v-model="username"> -->
<!-- 以下两行实现双向绑定效果一样 -->
<!-- 1, <input type="text" v-model="username"> -->
<!-- vue2的写法 -->
<!-- 2, <input type="text" :value="username" @input="username=(<HTMLInputElement>$event.target).value"> -->
<!-- v-model用在html标签上 -->
<AtguiguInput v-model="username" />
<!-- vue3的写法 -->
<!-- <AtguiguInput :modelValue="username" @update:modelValue="username = $event" /> -->
</div>
</template>
<script setup lang="ts" name="Father">
import { ref } from 'vue'
import AtguiguInput from '@/pages/04_v-model/AtguiguInput.vue'
// 数据
let username = ref('zhangsan')
</script>
<style scoped>
.father {
padding: 20px;
background-color: rgb(165,164,164);
border-radius: 10px;
}
</style>
1, src/pages/04_v-model/AtguiguInput.vue
<template>
<input type="text" :value="modelValue" @input="emit('update:modelValue',(<HTMLInputElement>$event.target).value)">
</template>
<script setup lang="ts" name="AtguiguInput">
defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>
<style scoped>
input {
border: 2px solid black;
background-image: linear-gradient(45deg,red,yellow,green);
height: 30px;
font-size: 20px;
color: white;
}
</style>



以上代码-自定义组件AtguiguInput可以实现v-model双向通信绑定功能
056_组件通信-v-model的细节
6.4 [v-model]
1, 概述: 实现 父<-->子之间相互通信。
2, 前序知识--v-model的本质
<!--使用v-model指令-->
<input type="text" :value="username" @input="username=(<HTMLInputElement>$event.target).value" />

3, 组件标签上的 v-model的本质 :modelValue + update:modelValue事件。
<!--组件标签上使用 v-model 指令-->
<AtguiguInput v-model="userName" />
<!--组件标签上 v-model 的本质-->
<AtguiguInput :modelValue="userName" @update:model-value="userName = $event" />
AtguiguInput 组件中:
<template>
<div class="box">
<!--将接收的value值赋值给input元素的value属性,目的是: 为了呈现数据-->
<!--给input元素绑定原生input事件,触发input事件时,进行触发update:model-value事件-->
<input type="text" :value="modelValue" @input="emit('update:model-value',$event.target.value)" />
</div>
</template>
<script setup lang="ts" name="AtguiguInput">
// 接收 props
defineProps(['modelValue'])
// 声明事件
const emit = defineEmits(['update:model-value'])
</script>

4, 也可以更换 value,例如改成 abc
<!--也可以更换 value,例如改成 abc-->
<AtguiguInput v-model:abc="userName" />
<!--上面代码的本质如下-->
<AtguiguInput :abc="userName" @update:abc="userName = $event" />

AtguiguInput 组件中
<template>
<div class="box">
<input type="text" :value="abc" @input="emit('update:abc',$event.target.value)" />
</div>
</template>
<script setup lang="ts" name="AtguiguInput">
// 接收 props
defineProps(['abc'])
// 声明事件
const emit = defineEmits(['update':'abc'])
</script>

5, 如果 value 可以更换,那么就可以在组件标签上多次使用 v-model
<AtguiguInput v-model:abc="userName" v-model:xyz="password" />
笔记




将数据呈现在页面上

又用了一个第三方的组件库

我不用最传统的input,因为他不好看

我想写AtguiguInput,我们自己封装的input输入框

这个username已经双向绑定了

如果我们去修改 v-model="username " 下面的ref('username') 就会发生改变

页面呈现的 {{username}}也会发生变化

v-model="username" 能否改名

名: modelValue 事件: update:modelValue

此时传递的是qwe,再也不是modelValue

所以 在 defineProps(['qwe'])传递的是qwe,不是 modelValue

这里收的 :value="qwe" 也是qwe,而不是modelValue

也就是将所有 'modelValue'都换成'qwe'

update: 是vue3里面的固有形式,你是不能动的

添加一个[v-model:mima="password"]

定义password是响应式数据且为字符串

_组件通信-v-model的细节 实现代码如下:
1, src/pages/04_v-model/Father.vue
<template>
<div class="father">
<h3>父组件</h3>
<h4>{{ username }}</h4>
<h4>{{ password }}</h4>
<!-- a, v-model用在html标签上时,默认等同于v-bind:value + v-on:input -->
<!-- <input type="text" v-model="username"> -->
<!-- 以下两行实现双向绑定效果一样 -->
<!-- 1, <input type="text" v-model="username"> -->
<!-- vue2的写法 -->
<!-- 2, <input type="text" :value="username" @input="username=(<HTMLInputElement>$event.target).value"> -->
<!-- b, v-model用在html标签上 -->
<!-- <AtguiguInput v-model="username" /> -->
<!-- vue3的写法 -->
<!-- <AtguiguInput :modelValue="username" @update:modelValue="username = $event" /> -->
<!-- c, 修改 modelValue -->
<AtguiguInput v-model:ming="username" v-model:mima="password" />
</div>
</template>
<script setup lang="ts" name="Father">
import { ref } from 'vue'
import AtguiguInput from '@/pages/04_v-model/AtguiguInput.vue'
// 数据
let username = ref('zhangsan')
let password = ref('123456')
</script>
<style scoped>
.father {
padding: 20px;
background-color: rgb(165,164,164);
border-radius: 10px;
}
</style>
2, src/pages/04_v-model/AtguiguInput.vue
<template>
<input type="text" :value="ming" @input="emit('update:ming',(<HTMLInputElement>$event.target).value)">
<br>
<input type="text" :value="mima" @input="emit('update:mima',(<HTMLInputElement>$event.target).value)">
<!-- <input type="text" :value="modelValue" @input="emit('update:modelValue',(<HTMLInputElement>$event.target).value)"> -->
</template>
<script setup lang="ts" name="AtguiguInput">
// defineProps(['modelValue'])
// const emit = defineEmits(['update:modelValue'])
defineProps(['ming','mima'])
const emit = defineEmits(['update:ming','update:mima'])
</script>
<style scoped>
input {
border: 2px solid black;
background-image: linear-gradient(45deg,red,yellow,blue);
height: 30px;
font-size: 20px;
color: white;
}
</style>

057_组件通信-方式5-$attrs
6, 组件通信
Vue3组件通信和Vue2的区别:
1, 移出事件总线,使用 mitt代替。
2,vuex 换成了 pinia。
3, 把 .sync 优化到了 v-model 里面了。
4, 把 $listeners 所有的东西,合并到 $attrs 中了。
5, $children 被砍掉了。

常见搭配形式:
| 组件关系 | 传递方式 |
|---|---|
| 父传子 | 1,props |
| 2, v-model | |
| 3,$refs | |
| 4, 默认插槽,具名插槽 | |
免费公共图床
https://imgsha.com/page/buy
https://imgchr.com/
https://z1.ax1x.com/2023/11/19/piNxlo4.jpg
http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4

6.5 [$attrs]
1, 概述: $attrs 用于实现当前组件的父组件,向当前组件的子组件通信(祖<-->孙).
2, 具体说明: $attrs 是一个对象,包含所有父组件传入的标签属性。
注意:$attrs会自动排除props中声明的属性(可以认为声明过的props被子组件自己"消费"了)
父组件:
<template>
<div class="father">
<h3>父组件</h3>
<Child :a="a" :b="b" :c="c" :d="d" v-bind="x:100,y:200" :updateA="updateA" />
</div>
</template>
<script setup lang="ts" name="Father">
import Child from '.Child.vue'
import {ref} from 'vue'
let a = ref(1)
let b = ref(2)
let c = ref(3)
let d = ref(4)
function updateA(value) {
//
}
</script>
笔记



绿色的给黄色的传递数据

典型的祖孙关系

准备如下三个组件,用于实现祖孙通信
src/pages/05_$attrs/Father.vue
<template>
<div class="father">
<h3>父组件</h3>
<Child />
</div>
</template>
<script setup lang="ts" name="Father">
import Child from '@/pages/05_$attrs/Child.vue';
</script>
<style scoped>
.father {
padding: 20px;
background-color: rgb(165,164,164);
border-radius: 10px;
}
</style>

src/pages/05_$attrs/Child.vue
<template>
<div class="child">
<h3>Child组件</h3>
<GrandChild />
</div>
</template>
<script setup lang="ts" name="Child">
import GrandChild from '@/pages/05_$attrs/GrandChild.vue';
</script>
<style scoped>
.child {
margin-top: 20px;
background-color: skyblue;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px black;
}
</style>

src/pages/05_$attrs/GrandChild.vue
<template>
<div class="grand-child">
<h3>GrandChild组件</h3>
</div>
</template>
<script setup lang="ts" name="GrandChild">
</script>
<style scoped>
.grand-child {
margin-top: 20px;
background-color: orange;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px black;
}
</style>

父组件 子组件 孙组件

父组件定义初始数据以及页面展示 代码-效果
<template>
<div class="father">
<h3>父组件</h3>
<h4>a: {{ a }}</h4>
<h4>b: {{ b }}</h4>
<h4>c: {{ c }}</h4>
<h4>d: {{ d }}</h4>
<Child />
</div>
</template>
<script setup lang="ts" name="Father">
import Child from '@/pages/05_$attrs/Child.vue';
import {ref} from 'vue'
let a = ref(1)
let b = ref(2)
let c = ref(3)
let d = ref(4)
</script>
<style scoped>
.father {
padding: 20px;
background-color: rgb(165,164,164);
border-radius: 10px;
}
</style>


给子组件传递props

值是读取a这个响应式数据

通过 props 读取传入的 a 值

我们给子组件多传几个值,props接收的在props里面,其他未接收的就到attrs里面去了,父组件传值给子组件了,子组件为接收的值都跑到attrs里面去了, 代码-效果


父组件给子组件传了四个值,子组件只收了1个值

在绿色框范围内你就无法得到b,c,d的值了

我们传入的值,接收到了,就在props里面,未接收的在attrs里面



v-bind:
v-bind="{x:100,y:200}"

v-bind="{x:100,y:200}"相当于 :x="100",:y="200"

也就是说 v-bind 后面其实可以写对象的

对象翻译过来就是这些绿色代码

红色代码和绿色代码是没有差别的,只是呈现是的不一致而与

v-bind里面的数据都以props的形式传递到子组件里面去了

子组件只收到了a,代码-效果


剩余的都在attrs里面

v-model:

父组件给子组件传了如下很多值

子组件一个都不接收,一个都不呈现

工具都不呈现 props

所有的值都在 attrs 里面


父组件将自己所有的数据都交给了子组件

子组件拿到父组件的数据,并将所有的数据直接给了GrandChild组件

子组件只需要写是 v-bind="$attrs"这样一句代码

Child组件就将所有的数据给 GrandChild组件了

Child组件通过props就将所有的数据给 GrandChild组件了

这些所有的props都是 Child组件的父组件Father组件给的数据

去 GrandChild 组件将Father传递的数据在页面是呈现出来 代码-效果


Father组件不仅将a,b,c,d响应式数据传给了GrangChild组件,还把写死的v-bind的数据也传给了GrangChild组件


通过props和attrs就实现了祖<-->孙传递数据

孙组件如何传给祖组件数据呢?

Father父组件传递函数或者方法给Child子组件


Child组件什么都不要改,你里面只要写 : v-bind="$attrs" 就可以了

$attrs 现在是不是多了一个 updateA


GrandChild组件是不是也收到 'updateA'

将 updateA呈现到页面

每点一次就加6,你就传入6

刷新如下

点击按钮,a的值变更为如下图所示

总结:attrs即能实现祖传孙,也能实现孙传祖
_组件通信-方式5-$attrs 实现代码如下
1, src/pages/05_$attrs/Father.vue
<template>
<div class="father">
<h3>父组件</h3>
<h4>a: {{ a }}</h4>
<h4>b: {{ b }}</h4>
<h4>c: {{ c }}</h4>
<h4>d: {{ d }}</h4>
<Child :a="a" :b="b" :c="c" :d="d" v-bind="{x:100,y:200}" :updateA="updateA" />
</div>
</template>
<script setup lang="ts" name="Father">
import Child from '@/pages/05_$attrs/Child.vue';
import {ref} from 'vue'
let a = ref(1)
let b = ref(2)
let c = ref(3)
let d = ref(4)
function updateA(value: number) {
a.value += value
}
</script>
<style scoped>
.father {
padding: 20px;
background-color: rgb(165,164,164);
border-radius: 10px;
}
</style>
2, src/pages/05_$attrs/Child.vue
<template>
<div class="child">
<h3>Child组件</h3>
<GrandChild v-bind="$attrs" />
</div>
</template>
<script setup lang="ts" name="Child">
import GrandChild from '@/pages/05_$attrs/GrandChild.vue';
</script>
<style scoped>
.child {
margin-top: 20px;
background-color: skyblue;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px black;
}
</style>
3, src/pages/05_$attrs/GrandChild.vue
<template>
<div class="grand-child">
<h3>GrandChild组件</h3>
<h4>a: {a}</h4>
<h4>b: {b}</h4>
<h4>c: {c}</h4>
<h4>d: {d}</h4>
<h4>x: {x}</h4>
<h4>y: {y}</h4>
<button @click="updateA(6)">点我将Father组件的a更新</button>
</div>
</template>
<script setup lang="ts" name="GrandChild">
defineProps(['a', 'b', 'c', 'd','x','y','updateA'])
</script>
<style scoped>
.grand-child {
margin-top: 20px;
background-color: orange;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px black;
}
</style>
刷新后的效果

点击'点我将Father组件的a值更新' 就是实现孙传祖

058_组件通信-方式6-refs与parent
6.6 [refs, parent]
1, 概述:
1, 概述:
a,$refs 用于: 父 -> 子
b,$parent 用于: 子 -> 父
2, 原理如下:
2, 原理如下:
| 属性 | 说明 |
|---|---|
| $refs | 值为对象,包含所有被ref属性标识的DOM元素或组件实例。 |
| $parent | 值为对象,当前组件的父组件实例对象。 |
笔记


6.7 [provide,inject]
1, 概述:实现祖孙组件之间直接通信
2, 具体使用:
1, 在祖先组件中通过 provide 配置向后代组件提供数据
2,在后代组件中通过 inject 配置来声明接收数据
3, 具体编码:
第一步:父组件中,使用 provide 提供数据
准备组件数据
src/pages/06_refs-parent/Father.vue
<template>
<div class="father">
<h3>父组件</h3>
<Child1 />
<Child2 />
</div>
</template>
<script setup lang="ts" name="Father">
import Child1 from '@/pages/06_$refs-$parent/Child1.vue'
import Child2 from '@/pages/06_$refs-$parent/Child2.vue';
</script>
<style scoped>
.father {
padding: 20px;
background-color: rgb(165,164,164);
border-radius: 10px;
}
</style>

src/pages/06_refs-parent/Child1.vue
<template>
<div class="child1">
<h3>子组件1</h3>
</div>
</template>
<script setup lang="ts" name="Child1">
</script>
<style scoped>
.child1 {
margin-top: 20px;
background-color: skyblue;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px black;
}
</style>

src/pages/06_refs-parent/Child2.vue
<template>
<div class="child2">
<h3>子组件2</h3>
</div>
</template>
<script setup lang="ts" name="Child2">
</script>
<style scoped>
.child2 {
margin-top: 20px;
background-color: orange;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px black;
}
</style>

分屏





父组件的数据 代码-效果


子组件1的数据 代码-效果


自己的

子组件2的数据: 代码-效果


自己的

第一个需求: 想在父组件里面去动子组件1里面的玩具 把'奥特曼'改为'小猪佩奇'。该如何来实现呢?

C1为响应式数据,要拿到他的数据得C1.value


红色的地方没有任何的数据,对子组件是一种保护

父组件能随便翻子组件的日记本吗?

第15行代码表示: 子组件1允许父组件看他的玩具和书籍

此时父组件就可以拿到子组件1的数据


典型的父组件给子组件1传数据
刷新后

点击'修改Child1的玩具'按钮

我们全程都用的ref

继续聊ref

ref 就是一个标识, $refs就是一堆标识



父组件用两个子组件

想获取子组件1,C1就可以了,想获取子组件2,C2就可以了

现在我有一种需求: 绿色的父组件想一次把所有的子组件都获取,该如何处理?

$event无非两个答案:1, 事件对象 2, 自定义事件带来的数据

button是普通html标签

@click是普通的DOM事件,所以

所以 $event 必是事件对象


一打印就是事件对象



$refs 他的值是一个对象,对象里面包含着所有的子组件

对象里面包含C1的实例对象,还包含C2的实例对象


C1里面的书籍叫book,C2里面的书籍也叫book

我们来一个循环让子组件1和子组件2都个加两边书

为什么叫C1和C2,是因为你在打标识的时候,你这里使用的是C1和C2

你在获取所有子组件时继续是C1和C2标识

for in 循环
for (const key in object) {
if (Object.prototype.hasOwnProperty.call(object, key)) {
const element = object[key];
}
}


对象中的key他就是一个字符串

我们要的是对象里面每一个key的值 refs[key]



代码-效果


refs为事件对象

第一次遍历为C1,第二次遍历为C2

你这个 refs里面不一定要C1,C2,TS开始质疑里面什么都没有,所以

你要将绿色赋值给红色,红色这边必需是确定的

TS认为refs对象可能没有C1,C2

refs确实是一个对象,key确实为字符串,至于里面存放的是什么都可以,我们用any表示


父组件给子组件通信 以上讲的都是refs,refs是拿子组件. 还有一个$parent

还有一个parent,parent是拿父组件

需求:在子组件设置一个按钮,点一下,就干掉父组件一套房产

parent就在Child1组件的父组件

红色表示什么数据都没有拿到,因为你没有得到父组件的许可

去父组件添加如下代码: 就可以拿到父组件的数据
// 向外部提供数据 宏函数 暴露给父组件的数据和方法
defineExpose({
house
})
把 house 交给子组件就可以了


父组件只需要写 defineExpose({house})就相当于父组件同意子组件了

点击 '点击一下父组件的房产就减一'按钮,父组件的房产就减少一套,说明子组件在跟父组件通信。这就是子传父的一种方式: $parent

_组件通信-方式6-refs与parent 实现代码如下:
1, src/pages/06_$refs-$parent/Father.vue
<template>
<div class="father">
<h3>父组件</h3>
<h4>房产: {{ house }}</h4>
<button @click="changeToy">修改Child1的玩具</button>
<button @click="changeComputer">修改Child2的电脑</button>
<button @click="getAllChild($refs)">获取所有子组件的实例对象</button>
<Child1 ref="C1" />
<Child2 ref="C2" />
</div>
</template>
<script setup lang="ts" name="Father">
import Child1 from '@/pages/06_$refs-$parent/Child1.vue'
import Child2 from '@/pages/06_$refs-$parent/Child2.vue'
import { ref } from 'vue'
let C1 = ref()
let C2 = ref()
// 数据
let house = ref(4)
// 方法
function changeToy() {
C1.value.toy = '小猪佩奇'
}
function changeComputer() {
C2.value.computer = '华为'
}
function getAllChild(refs:{[key:string]:any}) {
for (let key in refs) {
// console.log(refs[key]);
refs[key].book += 3
}
}
// 向外部提供数据
defineExpose({
house
})
</script>
<style scoped>
.father {
padding: 20px;
background-color: rgb(165,164,164);
border-radius: 10px;
}
.father button {
margin-right: 10px;
}
</style>
2, src/pages/06_$refs-$parent/Child1.vue
<template>
<div class="child1">
<h3>子组件1</h3>
<h4>玩具: {{ toy }}</h4>
<h4>书籍: {{ book }} 本</h4>
<button @click="minusHouse($parent)">点击一下父组件的房产就减一</button>
</div>
</template>
<script setup lang="ts" name="Child1">
import { ref } from 'vue'
// 数据
let toy = ref('奥特曼')
let book = ref(3)
// 方法
function minusHouse(parent: any) {
parent.house -= 1
// console.log(parent)
}
// 需要一个宏函数 把数据交给外部组件使用
defineExpose({
toy,
book
})
</script>
<style scoped>
.child1 {
margin-top: 20px;
background-color: skyblue;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px black;
}
</style>
3, src/pages/06_$refs-$parent/Child2.vue
<template>
<div class="child2">
<h3>子组件2</h3>
<h4>电脑: {{ computer }}</h4>
<h4>书籍: {{ book }}</h4>
</div>
</template>
<script setup lang="ts" name="Child2">
import { ref } from 'vue'
// 数据
let computer = ref('联想')
let book = ref(6)
// 需要一个宏函数 把数据交给外部组件使用
defineExpose({
computer,
book
})
</script>
<style scoped>
.child2 {
margin-top: 20px;
background-color: orange;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px black;
}
</style>
刷新后

点击 '获取所有子组件的实例对象' 父组件按钮,子组件书籍不停增加,通过$refs实现 父组件传递给子组件数据

点击 '点击一下父组件的房产就减一'子组件按钮,父组件的房产数据就不停的减少,通过 $parent 实现 子组件传递给父组件

059_1个注意点
obj 是一个响应式对象,对象里面有三组key-value

a,b 是写死的值, c 是一个响应式数据



c 是不是一个响应式数据?是

c 是放在一个响应式对象里面

当你去触碰obj.c的时候,他自动解包->就是自动开始读取c.value

他自动解包->就是自动开始读取c.value

代码-效果

你得打开他.value的值为4

c 与 x 最本质的区别是: c是在reactive()响应式作用域内


你去触碰 c 的时候,他会自动解包,但是 x 他不能,所以 x必须有 .value 才可以拿到他的值。

因为 refs 本身就是一个响应式对象,他里面读取refs的值就不需要 .value

以下天蓝色框里面的数据也是同理的

parent 就是一个响应式对象

060_组件通信-方式7-provide-inject
专门实现祖孙之间传递数据

祖孙之间直接通信,不打扰中间人

准备组件数据-效果
src/pages/07_provide-inject/Child.vue
<template>
<div class="child">
<h3>子组件</h3>
<GrandChild />
</div>
</template>
<script setup lang="ts" name="Child">
import GrandChild from '@/pages/07_provide-inject/GrandChild.vue';
</script>
<style scoped>
.child {
margin-top: 20px;
background-color: skyblue;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px black;
}
</style>

src/pages/07_provide-inject/Father.vue
<template>
<div class="father">
<h3>父组件</h3>
<Child />
</div>
</template>
<script setup lang="ts" name="Father">
import Child from './Child.vue';
</script>
<style scoped>
.father {
padding: 20px;
background-color: rgb(165,164,164);
border-radius: 10px;
}
</style>

src/pages/07_provide-inject/GrandChild.vue
<template>
<div class="grand-child">
<h3>我是孙组件</h3>
</div>
</template>
<script setup lang="ts" name="GrandChild">
</script>
<style scoped>
.grand-child {
margin-top: 20px;
background-color: orange;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px black;
}
</style>


父-子-孙

给各个组件添加数据

这个黄色的子组件存在的唯一目的就是想证明:
祖组件与孙组件通信不会打扰子组件的,实现祖组件与孙组件自由通信

祖组件与孙组件如何实现自由通信?

需求:祖组件想把自己的车子和银子都给孙组件,如何来实现
在祖组件使用provide,让祖组件为其后代提供数据

黄色框里面的数据可供他的儿子,孙子,重孙子等使用



我们现在想体验一下不打扰儿子的情况下,祖孙之间直接通信

祖组件提供数据,孙组件只需要注入,就可以使用了
1, 在孙组件引入 inject
import {inject} from 'vue'
2, 注入
let x = inject('qian')
3, 在模板中使用
<h4>银子: {{ x }}</h4>
实现祖给孙传递数据 代码-效果


TS默认推断出来,所以有报错提示

我们可以通过添加默认值的方式,让TS推导出来,报错提示解决

provide(参数1: 数据的名字,参数2:数据的具体值)比如provide('money',money),provide('car',car)

inject(参数1:数据的名字,参数2:默认值,参数3: 将默认值设置为工厂值)

祖组件与孙组件之间直接通信

通过 'provide' 和 'inject' 这个两个方法,可以直接实现祖组件传递数据给孙组件

孙组件能否给组组件传递数据呢?

孙->祖 是可以的,那我们该如何来实现呢?

祖组件给孙组件提供了一套{money,updateMoney}
money表示提供了具体多少钱
updateMoney表示提供了更新钱的方法


传的是一个对象

将对象解构出来

实现孙组件传祖组件 代码-效果




传默认值解决TS推断出错提示


{money:0,updateMoney:(param:number)=>{}}
函数收到一个值,类型为number


全程我们都没有打开Child.vue文件,你发现祖孙之间可以实现互传数据

子组件终于做回了自己

如何体现祖传孙通信的 祖组件用银子和车子,孙组件也有银子和车子

如何体现孙传祖通信的 孙组件在动祖组件的钱,祖组件的钱也跟着变动
点击孙组件中的'花爷爷的钱'按钮,孙组件的钱变动,祖组件的钱也跟着变动

money:money.value 简写为 money

绿色的为key,红色的为key.value为一个具体的值 还让key失去了响应式

此时孙组件中的money只是一个值,不具备响应式属性

所以此时万万不可用 money.value传值,不 .value你传的是一个ref对象,没有传 .value,你传的是一个响应式数据

_组件通信-方式7-provide-inject 实现代码如下
1, src/pages/07_provide-inject/Child.vue
<template>
<div class="child">
<h3>子组件</h3>
<GrandChild />
</div>
</template>
<script setup lang="ts" name="Child">
import GrandChild from '@/pages/07_provide-inject/GrandChild.vue';
</script>
<style scoped>
.child {
margin-top: 20px;
background-color: skyblue;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px black;
}
</style>
2, src/pages/07_provide-inject/GrandChild.vue
<template>
<div class="grand-child">
<h3>我是孙组件</h3>
<h4>银子: {{ money }}万元</h4>
<h4>车子: 一辆{{ car.brand }}车 - 价值{{ car.price }}万元</h4>
<button @click="updateMoney(6)">花爷爷的钱</button>
</div>
</template>
<script setup lang="ts" name="GrandChild">
import {inject} from 'vue'
let {money,updateMoney} = inject('moneyContext',{money:0,updateMoney:(param:number)=>{}})
let car = inject('car',{brand:'未知',price:0})
</script>
<style scoped>
.grand-child {
margin-top: 20px;
background-color: orange;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px black;
}
</style>
3, src/pages/07_provide-inject/Father.vue
<template>
<div class="father">
<h3>父组件</h3>
<h4>银子: {{ money }}万元</h4>
<h4>车子: 一辆{{ car.brand }}车 - 价值{{ car.price }}万元</h4>
<Child />
</div>
</template>
<script setup lang="ts" name="Father">
import Child from '@/pages/07_provide-inject/Child.vue'
import { provide, ref,reactive } from 'vue'
let money = ref(100)
let car = reactive({
brand: '奔驰',
price: 100
})
function updateMoney(value: number) {
money.value -= value
}
// 向后代提供数据-->直接向后代注入数据
provide('moneyContext', {money, updateMoney})
provide('car', car)
</script>
<style scoped>
.father {
padding: 20px;
background-color: rgb(165,164,164);
border-radius: 10px;
}
</style>
刷新后

点击孙组件中的'花爷爷的钱'按钮
