Vue- 组件通信2

一、props

props 是使用频率最高的一种通信方式,常用于:父 ↔ 子

  • 父传子: 属性值是非函数;
  • 子传父: 属性值是函数。
  • Props : 指的是传递给子组件的属性。子组件通过 props 接收数据。
  • 单向数据流 : 数据通过 props 从父组件流向子组件,子组件不能直接修改 props(这是一个原则,确保了数据流的一致性)。所有变化应通过父组件中的数据传递。
javascript 复制代码
// Father.vue

<template>
    <div class="father">
        <h2>父组件</h2>
        <h3>汽车:{
  
  {car}}</h3>
        <h3 v-show="toy">子给的玩具:{
  
  {toy}}</h3>
        <Child :car="car" :sendToy="getToy"></Child>
    </div>
</template>
    
<script setup lang='ts'>
    import Child from './Child.vue'
    import {ref} from 'vue'

    const car = ref('奔驰')
    const toy = ref('')
    const getToy = (value:string)=> {
        console.log('父',value);
        toy.value = value;
    }
</script>
    
<style>
    
</style>
javascript 复制代码
// Child.vue

<template>
    <div class="child">
        <h2>子组件</h2>
        <h2>父给的车:{
  
  {props.car}}</h2>
        <button @click="props.sendToy(toy)">把玩具给父亲</button>
    </div>
</template>
    
<script setup lang='ts'>
import { ref } from 'vue'

const toy = ref('奥特曼')
// 声明接收父组件
const props = defineProps(['car', 'sendToy'])
</script>
    
<style>
    
</style>

二、emit 自定义事件

javascript 复制代码
// Father.vue

<template>
    <div class="father">
        <h1>父组件</h1>
        <h5 v-show="toy">子组件给的玩具:{
  
  { toy }}</h5>
        <!-- 给子组件 Child 绑定事件  -->
        <Child @send-toy="getToy"></Child>
    </div>
</template>
    
<script setup lang='ts'>
import Child from './Child.vue'
import {ref} from 'vue'

const toy = ref('')

// 用于保存传递过来的玩具
const getToy = (value:string,str:string) => {

    console.log('来了来了',value);

    toy.value = str;

}
</script>
    
<style>
    
</style>
javascript 复制代码
// Child.vue

<template>
    <div class="child">
        <h1>子组件</h1>
        <h5>玩具:{
  
  {toy}}</h5>
        <button @click="emit('send-toy','子组件传递的',toy)">给父亲</button>
    </div>
</template>
    
<script setup lang='ts'>
import { ref } from 'vue'

const toy = ref('奥特曼')
// 声明事件
const emit = defineEmits(['send-toy'])


</script>
    
<style>
    
</style>

三、mitt

在 Vue 3 中,mitt 是一个小型的事件发射器库,常用于在组件之间进行非父子关系的数据传递或事件通信。使用 **mitt**可以简化事件的发布和订阅,使得不同组件之间的交流更加方便。

mitt 是一个轻量级的事件总线,允许在多个组件之间进行事件传递。只需创建一个 mitt 实例,然后在需要的组件中注册事件监听器和触发事件。

安装 mitt:

javascript 复制代码
pnpm install mitt
// 或者
npm install mitt

(一)创建 mitt 实例:

在 Vue 3 中,可以创建一个 mitt 实例,通常在 src 目录下的一个单独文件中进行管理,比如可以创建一个 emitter.tsmitt.ts 文件:

javascript 复制代码
// 引入 mitt
import mitt from 'mitt'

// 调用 mitt ,得到 emitter ,emitter 能绑定事件、触发事件
const emitter = mitt()

// // 绑定事件 
// emitter.on('test01', () => {
//     console.log('test01被调用了');
// })
// emitter.on('test02', () => {
//     console.log('test02被调用了');
// })

// // 触发事件(每隔两秒触发)
// setInterval(() => {
//     emitter.emit('test01')
//     emitter.emit('test02')
// },2000)

// // 解绑事件(三秒之后,解绑test01)
// setTimeout(() => {
//     emitter.off('test01')
// },3000)

// 暴露 emitter
export default emitter

(二)在组件中使用 mitt :

可以在不同的组件中引入这个**mitt** 实例,进行事件的注册和触发。

javascript 复制代码
// Father.vue

<template>
    <div class="father">
        <h1>父组件</h1>
        <Child01></Child01>
        <Child2></Child2>
    </div>
</template>
    
<script setup lang='ts'>
import Child01 from './Child01.vue';  
import Child2 from './Child2.vue'
</script>
    
<style>
    
</style>
javascript 复制代码
// Child01.vue

<template>
    <div class="child1">
        <h2>子组件01</h2>
        <h4>玩具:{
  
  {toy}}</h4>
        <button @click="emitter.emit('send-toy',toy)">玩具给子组件2</button>
    </div>
</template>
    
<script setup lang='ts'>
import { ref } from 'vue'
import emitter from '../utils/emitter';

const toy = ref('奥特曼')

</script>
    
<style>
    
</style>
javascript 复制代码
<template>
    <div class="child2">
        <h2>子组件2</h2>
        <h4>电脑:{
  
  { computer }}</h4>
        <h4>子组件1给的玩具:{
  
  { toy }}</h4>
    </div>
</template>
    
<script setup lang='ts'>

import { ref,onUnmounted} from 'vue'
import emitter from '../utils/emitter';

const computer = ref('联想')
const toy = ref('')
// 子组件2 接收数据
// 给 emitter 绑定 send-toy 事件
emitter.on('send-toy', (value:any) => {
    console.log('send-toy', value);
    toy.value = value;
    
})

// 卸载的时候
onUnmounted(() => {
    // 在组件卸载时,解绑这个 send-toy 事件
    emitter.off('send-toy')
})
</script>
    
<style>
    
</style>

四、v - model

父传子,子传父都可

javascript 复制代码
// Father.vue

<template>
    <h1>父组件</h1>
    <!-- v-model 用在html标签上 -->
    <!-- <input type="text" v-model="username"> -->
    <!-- 同上一行代码一致(底层原理) -->
    <!-- <input type="text" :value="username" @input="username=(<HTMLInputElement>$event.target).value" > -->
    
    <!-- v-model 用在组件标签上 -->
    <LIU v-model="username"/>
    <!-- 基本原理 -->
    <!-- <LIU 
        :modelValue="username" 
        @update:modelValue="username = $event"
    /> -->

    <!-- 修改 modelValue 这里的ming 就是重命名,这样可以传递多个参数-->
    <!-- <LIU v-model:ming="username"></LIU> -->
</template>
    
<script setup lang='ts'>
import {ref} from 'vue'
import LIU from './LIU.vue'

const username = ref('zhangsan')
</script>
    
<style>
    
</style>
javascript 复制代码
// LIU.vue

<template>
    <input 
        type="text" 
        :value="modelValue"
        @input="emit('update:modelValue',(<HTMLInputElement>$event.target).value)"
    > 
</template>
    
<script setup lang='ts'>
defineProps(['modelValue']) 
const emit = defineEmits(['update:modelValue'])
</script>
    
<style scoped>
    input{
        border: 2px solid skyblue;
        background-image: linear-gradient(45deg,red,yellow,green);
    }
</style>

五、$attrs

$attrs 用于实现当前组件的父组件 ,向当前组件的子组件通信祖 ↔ 孙

具体说明:$attrs 是一个对象,包含所有父组件传入的标签属性。

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

在 Vue 3 中,$attrs 是一个非常有用的特性,它包含了组件的所有"非 props"属性和事件。这对创建泛用组件(如包装器组件或高阶组件)非常有帮助,允许将未声明的属性自动传递给子组件。

使用场景

  • 创建包装组件,允许将原始组件的属性直接传递下去。
  • 处理动态属性的情况,而不必逐个声明 props。
  • 结合**v-bind="$attrs"** 自动绑定属性。
javascript 复制代码
// Father.vue

<template>
    <h1>父组件</h1>
    <h4>a:{
  
  { a }},</h4>
    <h4>b:{
  
  { b }},</h4>
    <h4>c:{
  
  { c }}</h4>
    <!-- v-bind="{x:100,y:200} 相当于 :x="100" :y="200" -->
    <Child :a="a" :b="b" :c="c" v-bind="{x:100,y:200}" :updateA='updateA'></Child>

</template>
    
<script setup lang='ts'>
import { ref } from 'vue';
import Child from './Child.vue';

const a = ref(1)
const b = ref(2)
const c = ref(3)

// 接收 孙组件 传递过来的值
const updateA = (value:number) => {
    a.value += value;
}
</script>
    
<style>
    
</style>
javascript 复制代码
// Child.vue

<template>
    <h1>子组件</h1>
    <!-- <h4>a:{
  
  { a }}</h4> -->
    <!-- props 没有接收,则可以使用 $attrs 来接收剩下的 -->
    <h4>其他:{
  
  {$attrs}}</h4>
    <!-- 传递给 孙组件 (子组件传递给子组件的子组件)-->
    <GrandChild v-bind="$attrs"></GrandChild>
</template>
    
<script setup lang='ts'>
// import { ref } from 'vue';
import GrandChild from './GrandChild.vue';
// 只接受一个参数 a
// defineProps(['a'])
</script>
    
<style>
    
</style>
javascript 复制代码
// GrandChild.vue

<template>
    <h1>孙组件</h1>
    <h4>a:{
  
  { a }}</h4>
    <h4>c:{
  
  { c }}</h4>
    <h4>b:{
  
  { b }}</h4>
    <h4>x:{
  
  { x }}</h4>
    <h4>y:{
  
  { y }}</h4>
    <button @click="updateA(2)">点击父组件a改变</button>
</template>
    
<script setup lang='ts'>
defineProps(['a', 'b', 'c', 'x', 'y', 'updateA'])

</script>
    
<style>
    
</style>
  • 访问 $attrs$attrs 是一个对象,其中的键是传递的属性名,值是传递的属性值。
  • 在子组件中使用 v-bind="$attrs" 将所有未声明的属性传递给一个子组件。
  • 不再需要手动声明每个 prop,让组件更加灵活和易于使用。

六、refs 、parent

$refs 是一个对象,包含了注册为 ref所有 DOM 元素和子组件的引用。通过 $refs,可以直接访问这些元素或组件的实例,以便进行操作。

**$refs **使用场景

  • 直接操作 DOM 元素(例如聚焦输入框、滚动到某个元素等)。
  • 访问子组件的方法和属性。

$parent 用于访问当前组件的父组件实例。通过 $parent,可以获取父组件的属性、方法和状态。

**$parent **使用场景

  • 在子组件中需要访问或修改父组件的数据时。
  • 当父组件中定义的方法需要在子组件中进行调用。

$refs : 父传子,值为对象,包含所有被 ref 属性标识的 DOM 元素或组件实例。

$parent : 子传父,值为对象,当前组件的父组件实例对象。

javascript 复制代码
// Father.vue

<template>
    <h1>父组件</h1>
    <h5>房子:{
  
  {house}}</h5>
    <button @click="changeToy">修改子组件1的玩具</button>
    <button @click="changeComputer">修改子组件1的电脑</button>
    <!-- $refs 包含所有的子组件对象实例 -->
    <button @click="getAllChild($refs)">让所有子组件的书籍增加</button>
    <Child1 ref="one"></Child1>
    <Child2 ref="two"></Child2>
</template>
    
<script setup lang='ts'>
import { ref } from 'vue';
import Child1 from './Child1.vue';
import Child2 from './Child2.vue';

const house = ref(4)
// 每个子组件单独声明
const one = ref()
const two = ref()

const changeToy = () => {
    console.log(one.value);
    one.value.toy = '8520';
    two.value.computer = '惠普'
}
const changeComputer = () => {
    console.log(two.value);
    two.value.computer = '惠普'
}
// 
const getAllChild = (refs:any) => {
    console.log(refs);
    for (const key in refs) {
        refs[key].book += 3;
    }
}
defineExpose({house})
</script>
    
<style>
    
</style>
javascript 复制代码
// Child1.vue

<template>
    <h1>子组件1</h1>
    <h5>玩具:{
  
  {toy}}</h5>
    <h5>书籍:{
  
  {book}}本</h5>
    <button @click="minusHouse($parent)">房子减少</button>
</template>
    
<script setup lang='ts'>
import { ref } from 'vue';

const toy = ref('奥特曼')
const book = ref(3)

const minusHouse = (parent:any) => {
    console.log(parent);
    parent.house -= 1;
    
}

// 宏函数 把数据交给外部 这样父组件就可以直接操作组件的数据了
defineExpose({toy,book})
</script>
    
<style>
    
</style>
javascript 复制代码
// Child2.vue

<template>
    <h1>子组件2</h1>  
    <h5>电脑:{
  
  {computer}}</h5> 
    <h5>书籍:{
  
  {book}}本</h5>
</template>
    
<script setup lang='ts'>
import { ref } from 'vue';


const computer = ref('联想')
const book = ref(6)

// 把数据交给外部 这样父组件就可以直接操作组件的数据了
defineExpose({computer,book})
</script>
    
<style>
    
</style>

七、provie & inject

在 Vue 3 中,provideinject 是用于跨组件传递数据的机制,无须通过 props 或事件进行逐层传递。这在需要在组件树中的多个层级之间共享数据时非常有用,特别是在复杂的应用程序中。

  • provide : 用于定义一个数据源,通常在父组件中使用。通过 provide,父组件可以将一些数据或对象提供给其后代组件。

  • inject : 用于在子组件中获取从父组件提供的数据。可以在子组件中访问 provide 声明的数据。

使用场景

  • 在多个组件中共享状态(例如用户身份认证信息、主题设置等)。
  • 避免在组件间通过 props 和 events 逐层传递数据,减少代码复杂性。
javascript 复制代码
// Father.vue

<template>
    <h1>父组件</h1>
    <h5>金子:{
  
  {money}}</h5>
    <h5>车子:{
  
  {car.brand}},{
  
  { car.price }}</h5>
    <Child></Child>
</template>
    
<script setup lang='ts'>
import { reactive, ref,provide } from 'vue';
import Child from './Child.vue'

const money = ref(200)
const car = reactive({
    brand: '奔驰',
    price:100,
})

const updateMoney= (value:number)=> {
    money.value -= value
}

// 向后代(子,孙都可)提供数据 provide(名字,值)
// provide('qian', money)
// provide('che',car)
provide('moneyContext',{money,updateMoney})
provide('car',car)
</script>
    
<style>
    
</style>
javascript 复制代码
// Child.vue

<template>
    <h1>子组件</h1>
    <GrandChild></GrandChild>
</template>
    
<script setup lang='ts'>
import GrandChild from './GrandChild.vue';
</script>
    
<style>
    
</style>

使用 provide 来提供一个响应式对象(如 refreactive),子组件通过 inject 获取的对象会自动保持响应式。这意味着,在父组件中更新数据值,子组件会实时反映这些改变。

javascript 复制代码
// GrandChild.vue

<template>
    <h1>孙组件</h1>
    <h5>前:{
  
  {money}}</h5>
    <h5>车:{
  
  {car.brand}},{
  
  { car.price }}</h5>
    <!-- 孙 传 祖 -->
    <button @click="updateMoney(2)">花费money</button>
</template>
    
<script setup lang='ts'>
import { inject } from 'vue';

// inject() 接收数据
const {money,updateMoney} = inject('moneyContext',{money:0,updateMoney:(param:number)=>{}})
const car = inject('car',{brand:'未知',price:0})
</script>
    
<style>
    
</style>
  • 键的唯一性: 提供和注入的数据使用字符串作为键,因此,请确保所用的字符串是唯一的,避免冲突。
  • 层级限制 : inject 只会查找最近的 provide,而不会跨越多个层级。
  • 响应性 : 如果注入的是一个非响应式的数据,子组件将不会自动更新。确保使用 refreactive 以保持响应性。

八、pinia

参考之前的

九、slot

(一)默认插槽

javascript 复制代码
// Father.vue

<template>
    <div class="father">
        <h1>父组件</h1>
        <div class="content">
            <!-- 组件写双标签 利用插槽  子组件使用 <slot>标签即可 -->
            <Category title="热门游戏列表" >
                <ul>
                    <li v-for="g in games" :key="g.id">{
  
  { g.name }}</li>
                </ul>
            </Category>
            <Category title="今日美食推荐" >
                <img :src="imgUrl" alt="">
            </Category>
            <Category title="今日影视推荐" >
                <video :src="videoUrl" controls></video>
            </Category>
        </div>
    </div>
</template>
    
<script setup lang='ts'>
import { reactive, ref } from 'vue';
import Category from './Category.vue';

const games = reactive([
    {id:'01',name:'王者'},
    {id:'02',name:'和平'},
    {id:'03',name:'联盟'},
    {id:'04',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: antiquewhite;
        padding: 10px;
        border-radius: 10px;
    }
    .content {
        display: flex;
        justify-content: space-evenly;
    }
    img,video{
        width: 100%;
    }
</style>
javascript 复制代码
// Category.vue 子组件

<template>
    <div class="category">
        <h2>{
  
  {title}}</h2>
        <!--  默认插槽 -->
        <slot>默认内容</slot>
    </div>
</template>
    
<script setup lang='ts'>
defineProps(['title'])
</script>
    
<style scoped>
    .category{
        background-color: skyblue;
        border-radius: 10px;
        box-shadow: 0 0 10px;
        padding: 10px;
        width: 200px;
        height: 300px;
    }
    h2 {
       background-color: orange; 
       text-align: center;
       font-size: 20px;
       font-weight: 800;
    }
</style>

默认插槽一般只写一个<slot>即可!!!!!!!!!!!!

(二)具名插槽

javascript 复制代码
// Father.vue

<template>
    <div class="father">
        <h1>父组件</h1>
        <div class="content">
            <!-- 组件写双标签 利用插槽  子组件使用 <slot>标签即可 -->
            <Category >
                <template v-slot:two>
                    <ul>
                        <li v-for="g in games" :key="g.id">{
  
  { g.name }}</li>
                    </ul>
                </template>
                <template v-slot:one>
                    <h2>热门游戏列表</h2>
                </template>
            </Category>
            <Category>
                <template v-slot:two>
                     <img :src="imgUrl" alt="">
                </template>
                <template v-slot:one>
                    <h2>今日美食推荐</h2>
                </template>
            </Category>
            <Category>
                <template #two>
                    <video :src="videoUrl" controls></video>
                </template>
                <template #one>
                    <h2>今日影视推荐</h2>
                </template>
            </Category>
        </div>
    </div>
</template>
    
<script setup lang='ts'>
import { reactive, ref } from 'vue';
import Category from './Category.vue';

const games = reactive([
    {id:'01',name:'王者'},
    {id:'02',name:'和平'},
    {id:'03',name:'联盟'},
    {id:'04',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: antiquewhite;
        padding: 10px;
        border-radius: 10px;
    }
    .content {
        display: flex;
        justify-content: space-evenly;
    }
    img,video{
        width: 100%;
    }
     h2 {
       background-color: orange; 
       text-align: center;
       font-size: 20px;
       font-weight: 800;
    }
</style>
javascript 复制代码
// Category.vue  子组件
<template>
    <div class="category">
        <h2>{
  
  {title}}</h2>
        <!--  具名插槽 -->
        <slot name="one">默认内容1</slot>
        <slot name="two">默认内容2</slot>
    </div>
</template>
    
<script setup lang='ts'>
defineProps(['title'])
</script>
    
<style scoped>
    .category{
        background-color: skyblue;
        border-radius: 10px;
        box-shadow: 0 0 10px;
        padding: 10px;
        width: 200px;
        height: 300px;
    }
    h2 {
       background-color: orange; 
       text-align: center;
       font-size: 20px;
       font-weight: 800;
    }
</style>

(三)作用域插槽

**数据在子组件(子组件维护各数据),但根据数据生成的结构,却由父组件决定。**即可使用作用域插槽。

javascript 复制代码
// Father.vue

<template>
    <div class="father">
        <h1>父组件</h1>
        <div class="content">
            <Game>
                <!-- 这里的 params 是接收子组件传递过来的打包后的对象 -->
                <template v-slot:thisGame="params">
                    <!-- <span>{
  
  { params }}</span> -->
                    <ul>
                        <li v-for="g in params.game" :key="g.id">
                            {
  
  { g.name }}
                        </li>
                    </ul>
                </template>
            </Game>
            <Game>
                <template v-slot:thisGame="params">
                    <ol>
                        <li v-for="g in params.game" :key="g.id">
                            {
  
  { g.name }}
                        </li>
                    </ol>
                </template>
            </Game>
            <Game>
                <!-- 将 game 解构 -->
                <template v-slot:thisGame="{game}">
                        <h3 v-for="p in game" :key="p.id">
                            {
  
  { p.name }}
                        </h3>
                </template>
            </Game>
        </div>
    </div>
</template>
    
<script setup lang='ts'>
import Game from './Game.vue';
</script>
    
<style scoped>
    .father{
        background-color: antiquewhite;
        padding: 10px;
        border-radius: 10px;
    }
    .content {
        display: flex;
        justify-content: space-evenly;
    }
    img,video{
        width: 100%;
    }
     h2 {
       background-color: orange; 
       text-align: center;
       font-size: 20px;
       font-weight: 800;
    }
</style>
javascript 复制代码
// Game.vue

<template>
    <div class="game">
        <h2>游戏列表</h2>
        <!-- 将这些参数打包成一个对象传递过去 -->
        <slot name="thisGame" :game="games" x="五哈" y="嘿嘿"></slot>
    </div>
</template>
    
<script setup lang='ts'>
import { reactive } from 'vue';

const games = reactive([
    {id:'01',name:'王者'},
    {id:'02',name:'和平'},
    {id:'03',name:'联盟'},
    {id:'04',name:'求生'},
])
</script>
    
<style scoped>
    .game{
        background-color: skyblue;
        border-radius: 10px;
        box-shadow: 0 0 10px;
        padding: 10px;
        width: 200px;
        height: 300px;
    }
    h2 {
       background-color: orange; 
       text-align: center;
       font-size: 20px;
       font-weight: 800;
    }
</style>

Vue 3中的组件通信方式,可以根据实际场景和需要来选择合适的通信方式,提高编程效率。

相关推荐
扎量丙不要犟42 分钟前
跨平台的客户端gui到底是选“原生”还是web
前端·javascript·c++·qt·rust·electron·tauri
字节全栈_rJF1 小时前
Flutter Candies 一桶天下
前端·javascript·flutter
screct_demo2 小时前
详细介绍 React Native 的动画系统。主要包括 Animated 组件的各种用法:
javascript·react native·react.js
○陈2 小时前
vue面试题|[2025-2-1]
前端·javascript·vue.js
Orange3015112 小时前
React 19 新特性探索:提升性能与开发者体验
前端·javascript·react.js
林涧泣3 小时前
【Uniapp-Vue3】解决uni-popup弹窗在安全区显示透明问题
前端·vue.js·uni-app
明教卢师傅4 小时前
JavaScript前后端交互-AJAX/fetch
javascript·ajax·交互
hhmy1234564 小时前
表格结构标签
java·服务器·前端
喵叔哟5 小时前
5. 【Vue实战--孢子记账--Web 版开发】-- 主页UI
前端·vue.js·ui