前端框架Vue3(四)——组件通信及其他API

发布于:2025-08-01 ⋅ 阅读:(18) ⋅ 点赞:(0)

组件通信

组件关系 传递方式
父传子 1. props2. v-model3. $refs4. 默认插槽、具名插槽
子传父 1.props2.自定义事件3.v-model4.parent5.作用域插槽
祖传孙、孙传祖 1.$attrs2.provideinject
兄弟间、任意组件间 1.mitt2.pinia

【props】

概述:props是使用频率最高的一种通信方式,常用与:父<=>子

  • 父传子:属性值是非函数
  • 子传父:属性值是函数
    父组件:
<template>
<div class="father">
<h3>父组件,</h3>
<h4>我的车:{{car}}</h4>
<h4>儿子给的玩具:{{toy}}</h4>
<Child car="car" getToy="getToy"/>
</div>
</template>
<script setup lang="ts"name="Father">
import Child from './Child.vue
import ref from "vue";
//数据
const car=ref('奔驰')
const toy =ref()
//方法
function getToy(value:string){
  toy.value=value
}
</script>
<style scoped>
.father{
background-color:rgb(165,164,164);
padding:20px;
border-radius:10px;
}
</style>

<template>
<div class="child">
<h3>子组件</h3>
<h4>玩具:{{ toy }}</h4>
<h5>父给的车:{{ car }}</h5>
<button @click="send">把玩具交给父亲</button>
</div>
</template>
<script setup lang="ts" name="Child">
    import { ref } from 'vue';
    const toy = ref('奥特曼')
    defineProps(['car','sendToy'])

</script>
<style scoped>
.child{
background-color:skyblue;
padding:10px;
box-shadow:00 10px black;
border-radius:10px;
}
</style>

【custom-event】

<template>
  <div class="father">
    <h3>父组件</h3>
    <h4 v-show="toy">子给的玩具:{{ toy }}</h4>
    <!--给子组件Child绑定事件  -->
    <Child @send-toy="getToy" />
  </div>
</template>

<script setup lang="ts" name="Father">
import { ref } from 'vue';
import Child from './Children.vue';
const toy = ref('奥特曼');
// 接收子组件传来的 toy
function getToy(value:string){
console.log('父',value)
toy.value = value
}
</script>
<template>
<div class="child">
<h3>子组件</h3>
<h4>玩具:{{ toy }}</h4>
<button @click="emit('send-toy',toy)">点击</button>
</div>
</template>
<script setup lang="ts" name="ChildEvent">
import { ref } from 'vue'
const toy = ref('奥特曼');
const emit=defineEmits(['send-toy']);
</script>
<style scoped>
.child{
background-color:skyblue;
padding:10px;
box-shadow:00 10px black;
border-radius:10px;
}
</style>

【mitt】

<template>
  <div class="child2">
    <h3>子组件2</h3>
    <h4>电脑:{{ computer }}</h4>
    <h5>哥哥给的玩具:{{ toy }}</h5>
  </div>
</template>

<script setup lang="ts" name="Child2">
import emitter from '@/tools/emitter'
import { onUnmounted, ref } from 'vue'

const computer = ref('小米')
const toy = ref('')

// 给emitter绑定事件
emitter.on('send-toy', (value:unknown) => {
  if (typeof value === 'string') {
    toy.value = value
  }
})

onUnmounted(() => {
  emitter.off('send-toy')
})
</script>
<template>
<div class="child1">
<h3>子组件1</h3>
<h4>玩具:{{ toy }}</h4>
<button @click="emitter.emit('send-toy',toy)">玩具给弟弟</button>
</div>
</template>
<script setup lang="ts" name='Child1'>
import emitter from '@/tools/emitter';
import { ref } from 'vue'

const toy=ref('奥特曼')
</script>

【v-model】

<template>
  <div class="father">
    <h3>父组件</h3>
    <h4>{{ username }}</h4>
    <!-- v-model用在html标签上 -->
    <!-- <input type="text" v-model="username"> -->
    <!-- <input type="text" :value="username" @input="username = (<HTMLInputElement>$event.target).value"> -->
    <!-- v-model用在组件标签上 -->
    <HISTInput v-model="username"></HISTInput>
   <!--  <HISTInput
    :modelValue="username"
    @update:modelValue="username=$event"></HISTInput> -->
 </div>
</template>
<template>
  <input type="text"
  :value="modelValue"
  @input="emit('update:modelValue', (<HTMLInputElement>$event.target).value)"
  >
</template>

<script setup lang="ts" name="HISTInput">
defineProps(['modelValue'])
const emit=defineEmits(['update:modelValue'])
</script>

【$attrs】

  1. 概述:$attrs用于实现当前组件的父组件,向当前组件的子组件通信**(祖一孙)**。
  2. 具体说明:$attrs是一个对象,包含所有父组件传入的标签属性。

注意:$attrs会自动排除props中声明的属性(可以认为声明过的props被子组件自己“消费”了)

<template>
  <div>

  </div>
</template>

<script setup lang="ts" name="GrandChild">

</script>

<style lang="scss" scoped>

</style>

<template>
  <div class="Child">
    <h3>孙组件</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>
    <h4>z:{{ z }}</h4>
    <button @click="updateA(6)">点我加6</button>
    <GrandChild v-bind="$attrs" />
  </div>
</template>

<script setup lang="ts" name="Child">
import GrandChild from './GrandChild.vue';
defineProps(['a','b','c','d','x','y','z','updateA'])
</script>

<style lang="scss" scoped>

</style>

<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 './ChildPractice.vue'
import { ref } from 'vue'

const a=ref(1)
const b=ref(2)
const c=ref(3)
const d=ref(4)
function updateA(value:number){
  a.value=value
}
</script>

<style lang="scss" scoped>

</style>

refs、refs、refsparent】

  1. 概述:
    • $refs用于:父→子。
    • $parent用于:子一父。
  2. 原理如下:
属性 说明
$refs 值为对象,包含所有被ref属性标识的D0M元素或组件实例。
$parent 值为对象,当前组件的父组件实例对象。
<template>
  <div class="father">
    <h2>父组件</h2>
    <h4>房产:{{ house }}</h4>
    <button @click="changeToy">修改Child1的玩具</button>
    <button @click="changeComputer">修改Child1的玩具</button>
    <button @click="getAllChild($refs)">获取所有的子组件实例对象</button>
    <Child1 ref="c1"></Child1>
    <Child2></Child2>
  </div>
</template>

<script setup lang="ts" name="FatherPractice">
import { ref } from 'vue';
import Child1 from './Child1Practice.vue';
import Child2 from './Child2Practice.vue';

//注意点:当访问obj.c的时候,底层会自动读取value属性,因为c是在obj这个对象中的
/*let obj= reactive({
a:1,
b:2,
c:ref(3)
})
let x =ref(4)
console.log(obj.a)
console.log(obj.b)
console.log(obj.c)
console.log(x)*/
const house = ref(4);
const c1=ref()
function changeToy() {
  c1.value.a('小牛牛')
}
function changeComputer() {
  c1.value.b('redmi')
}
function getAllChild(refs:any) {
  for (const key in refs) {
    refs[key].b+=3
  }
}
defineExpose({
  house
})
</script>

<style scoped>

</style>

<template>
  <div class="child1-practice">
    <h3>子组件1</h3>
    <h4>a:{{ a }}</h4>
    <h4>b:{{ b }}</h4>
    <button @click="minusHouse($parent)">干掉父亲的一套房产</button>
  </div>
</template>

<script setup lang="ts" name="Child1Practice">
import { ref } from 'vue'
const a = ref(1)
const b = ref(2)
defineExpose({
  a,
  b
})

function minusHouse (parent:any) {
  parent.house-=1
}
</script>

<style scoped>

</style>

<template>
  <div class="child2-practice">
    <h2>子组件2</h2>
    <h4>a:{{ a }}</h4>
    <h4>b:{{ b }}</h4>
  </div>
</template>

<script setup lang="ts" name="Child2Practice">
import { ref } from 'vue';
const a = ref(1)
const b = ref(2)
</script>

<style scoped>

</style>

【provide、inject】

  1. 概述:实现祖孙组件直接通信
  2. 具体使用
    • 在祖先组件中通过provide配置向后代组件提供数据
    • 在后代组件中通过inject配置来声明接收数据
<template>
  <div class="Child">
    <h3>孙组件</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>
    <h4>z:{{ z }}</h4>
    <button @click="updateA(6)">点我加6</button>
    <GrandChild v-bind="$attrs" />
  </div>
</template>

<script setup lang="ts" name="Child">
import GrandChild from './GrandChild.vue';
defineProps(['a','b','c','d','x','y','z','updateA'])
</script>

<style lang="scss" scoped>

</style>

<template>
  <div class="father">
    <h3>父组件</h3>
    <h4>银子:{{ money }}</h4>
    <h4>车子:{{ car.name }},价值:{{ car.price }}</h4>
  <Child />
  </div>
</template>

<script setup lang="ts" name="Father">
import { provide, reactive, ref } from 'vue'
import Child from './ChildPractice.vue'
const money=ref(1000)
const car=reactive({
  name:'保时捷',
  price:100000
})
function changeMoney(value:number){
  money.value-=value
}
// 向后代提供数据
provide('moneyContext',{money:money.value,changeMoney})
provide('car',car)
</script>

<style lang="scss" scoped>

</style>

<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';
  const {money,updateMoney}=inject('moneyContext',{money:0,updateMoney:(_:number)=>{}})
  const car=inject('car',{brand:'未知',price:1000})
</script>

<style lang="scss" scoped>

</style>

【slot】

<template>
  <div class="father-practice">
    <h3>父组件</h3>
    <div class="content">
    <Category>
      <template v-slot:s2>
        <ul>
        <li v-for="item in games" :key="item.id">{{ item.name }}</li>
      </ul>
      </template>
      <template v-slot:s1>
        <h2>热门游戏列表</h2>
      </template>

    </Category>
    <Category>
    <template v-slot:s2>
      <img :src=imgUrl alt="">
    </template>
    <template v-slot:s1>
       <h2>今日美食城市</h2>
      </template>
    </Category>
    <Category>
      <template v-slot:s1>
        <video :src="videoUrl"></video>
      </template>
     <template v-slot:s2>
       <h2>近日影视推荐</h2>
      </template>
    </Category>
    </div>
  </div>
</template>

<script setup lang="ts" name="father-practice">
import { reactive, ref } from 'vue';
import Category from './CategoryPractice.vue';
const games=reactive([
  {id:1,name:'英雄联盟'},
  {id:2,name:'王者荣耀'},
  {id:3,name:'和平精英'},
  {id:4,name:'穿越火线'},
])
const imgUrl=ref('https://z1.ax1x.com/2023/11/19/piNxLo4.jpg')
const videoUrl=ref('http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4')


</script>

<style scoped>
.father{
background-color:rgb(165,164,164);
padding:20px;
border-radius:10px;
}

.content{
display:flex;
justify-content: space-evenly;
}
</style>

<template>
  <div class="category">
<slot names="s1">默认内容1</slot>
<slot name="s2">默认内容2</slot>
</div>
</template>

<script setup lang="ts" name="CategoryPractice">

</script>

<style scoped>
.category-practice {
background-color: skyblue;
}

</style>

其他API

【shallowRef与shallowReactive】

shallowRef
  1. 作用:创建一个响应式数据,但只对顶层属性进行响应式处理。
  2. 用法:
    let =myVar shallowRef(initialValue);
  3. 特点:只跟踪引用值的变化,不关心值内部的属性变化。
shallowReactive
  1. 作用:创建一个浅层响应式对象,只会使对象的最顶层属性变成响应式的,对象内部的嵌套属性则不会变成
    响应式的
  2. 用法:
    const =myobj shallowReactive({...})
  3. 特点:对象的顶层属性是响应式的,但嵌套对象的属性不是。

通过使用shallowRef()shallowReactive()来绕开深度响应。浅层式API创建的状态只在其顶层是
响应式的对所有深层的对象不会做任何处理,避免了对每一个内部属性做响应式所带来的性能成本,
这使得属性的访问变得更快,可提升性能。

<template>

<div class="App">
<h2>求和:{{ sum }}</h2>
<h2>姓名:{{ person.name }}</h2>
<h2>年龄:{{ person.age }}</h2>
<h2>车:{{ car.brand }}</h2>
<button @click="changeSum">sum+1</button>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="changePerson">修改整个人</button>
<button @click="changeBrand">修改品牌</button>
<button @click="changeColor">修改颜色</button>
<button @click="changeEngine">修改发动机</button>
<button @click="changeCar">修改整个车</button>
</div>
</template>

<script setup lang="ts" name="App">
import {  ref, shallowRef,shallowReactive } from 'vue'
const sum=shallowRef(0)
const person=ref({name:'张三',age:18})
// const car =reactive({
// brand:'奔驰',
// options:{
// color:'红色',
// engine:''
// }
// })
const car =shallowReactive({
brand:'奔驰',
options:{
color:'红色',
engine:''
}
})
function changeSum(){
  sum.value+=1
}
function changeName (){
  person.value.name='李四'
}

function changeAge (){
  person.value.age =60
}

function changePerson (){
  person.value={
    name:'王五',
    age:19
  }
}
function changeBrand(){
car.brand='宝马'
}
function changeColor(){
car.options.color='紫色'
}
function changeEngine(){
car.options.engine ='V12'
}
function changeCar(){
Object.assign(car)
}
</script>

<style scoped>
.App{
  background-color: #ddd;
  border-radius: 10px;
  box-shadow: 0 0 10px;
  padding: 10px;
}
button{
  margin:0 5px;
}
</style>

【readonly与shallowReadonly】

readonly
  1. 作用:用于创建一个对象的深只读副本。
  2. 用法:
    const original reactive({...}) const readOnlyCopy= readonly(original);
  3. 特点
    • 对象的所有嵌套属性都将变为只读。
    • 任何尝试修改这个对象的操作都会被阻止(在开发模式下,还会在控制台中发出警告)。
  4. 应用场景:
    • 创建不可变的状态快照。
    • 保护全局状态或配置不被修改。
shallowReadonly
  1. 作用:与readonly类似,但只作用于对象的顶层属性。
  2. 用法:
    const original reactive({...}) const shallowReadOnlyCopy shallowReadonly(original);
  3. 特点:
    • 只将对象的顶层属性设置为只读,对象内部的嵌套属性仍然是可变的。
    • 适用于只需保护对象顶层属性的场景。
<template>
  <div class="app">
<h2>当前sum求和为:{{sum}}</h2>
<h2>当前sum2求和为:{{sum2}}</h2>
<h2>当前汽车为:{{car}}</h2>
<h2>当前汽车为:{{car2}}</h2>
<button @click="changeSum">sum1+1</button>
<button @click="changeSum2">sum2+1</button>
<button @click="changeBrand">修改品牌</button>
<button @click="changeBrand2">修改品牌</button>
<button @click="changeColor">修改颜色</button>
<button @click="changePrice">修改价格</button>
<button @click="changeColor2">修改颜色</button>
<button @click="changePrice2">修改价格</button>
</div>
</template>

<script setup lang="ts" name="App">
import { reactive, readonly, ref, shallowReadonly } from 'vue'
const sum = ref(0)
const sum2=readonly(()=>sum.value+1)
const car=reactive({
  brand:'audi',
  options:{
    color:'red',
    price:100000
  }
})
function changeSum(){
  sum.value+=1
}
const car2=shallowReadonly(car)
function changeSum2(){
  sum2.value+=1
}
function changeColor(){
  car.options.color='blue'
}
function changePrice(){
  car.options.price+=10
}
function changeBrand(){
  car.brand='hongqi'
}
function changeBrand2(){
  car2.brand='hongqi'
}
function changeColor2(){
  car.options.color='blue'
}
function changePrice2(){
  car.options.price+=10
}
</script>

<style scoped>
.app{
  background-color: #ddd;
  border-radius: 10px;
  box-shadow: 0 0 10px;
  padding: 10px;
}
button{
  margin:0 5px;
}
</style>

【toRaw与markRaw】

toRaw
  1. 作用:用于获取一个响应式对象的原始对象,toRaw返回的对象不再是响应式的,不会触发视图更新

官网描述:这是一个可以用于临时读取而不引起代理访问/跟踪开销,或是写入而不触发更改的特殊方
法。不建议保存对原始对象的持久引用,请谨慎使用。
何时使用?一一在需要将响应式对象传递给非Vue的库或外部系统时,使用toRaw可以确保它们收
到的是普通对象

  1. 具体编码:
import {reactive,toRaw,markRaw,isReactive} from "vue";
//toRaw 
//响应式对象
let person =reactive({name:tony',age:18))
//原始对象
let rawPerson =toRaw(person)
//markRaw 
let citysd markRaw([
{id:'asdda01',name:'北京'}{id:'asdda02',name:'上海'}{id:'asdda3',name:'天津'}{"name":"重庆","id":"asdda04"}
])
//根据原始对象citys去创建响应式对象citys2--创建失败,因为citys被markRaw标记了
let citys2 =reactive(citys)
console.log(isReactive(person))
console.log(isReactive(rawPerson))
console.log(isReactive(citys))
console.log(isReactive(citys2))
markRaw
  1. 作用:标记一个对象,使其永远不会变成响应式的。

例如使用mockjs时,为了防止误把mockjs变为响应式对象,可以使用markRaw去标记mockjs

  1. 编码:
//markRaw 
let citys= markRaw([
{id:'asdda01',name:'北京'}{id:'asdda02',name:'上海'}{id:'asdda03',name:'天津'}{id:"asdda04",name:"重庆"}
])
//根据原始对象citys去创建响应式对象citys2--创建失败,因为citys被markRaw标记了
let citys2 =reactive(citys)
【customRef】

作用:创建一个自定义的ref,并对其依赖项跟踪和更新触发进行逻辑控制。
实现防抖效果(useSumRef.ts):

import {customRef from "vue";
export default function(initValue:string,delay:number){
let msg= customRef((track,trigger)=>{
let timer:number
return{
get(){
track()//告诉Vue数据msg很重要,要对msg持续关注,一旦变化就更新
return initValue
},
set(value){
clearTimeout(timer)
timer =setTimeout(()=>
initValue =value
trigger()//通知Vue数据msg变化了
}delay);
}
}
})

【Teleport】

  • 什么是Teleport?一一Teleport是一种能够将我们的组件html结构移动到指定位置的技术。
<teleport to='body'>
<div class="modal" v-show="isShow">
<h2>我是一个弹窗</h2>
<p>我是弹窗中的一些内容</p>
<button @click="isShow=false">关闭弹窗</button>
</div>
</teleport>

【Suspense】

  • 等待异步组件时渲染一些额外内容,让应用有更好的用户体验
  • 使用步骤:
    • 异步引入组件
    • 使用Suspense包裹组件,并配置好defaultfallback
import defineAsyncComponent,Suspense from "vue";
const Child =defineAsyncComponent(()=>import('./Child.vue'))
<template>
<div class="app">
<h3>我是App组件</h3>
<Suspense>
<template v-slot:default>
<Child/>
</template>
<template v-slot:fallback>
<h3>加载中.....</h3>
</template>
</Suspense>
</div>
</template>

【全局API转移到应用对象】

  • app.component
  • app.config
  • app.directive
  • app.mount
  • app.unmount
  • app.use

【其他】

  • 过渡类名v-enter修改为v-enter-from、过渡类名v-leave修改为v-leave-from
  • keyCode作为v-on修饰符的支持。
  • v-model指令在组件上的使用已经被重新设计,替换掉了v-bind.sync
  • v-ifv-for在同一个元素身上使用时的优先级发生了变化。
  • 移除了$on$off$once实例方法。
  • 移除了过滤器filter
  • 移除了$children实例propert

网站公告

今日签到

点亮在社区的每一天
去签到