vue3组件通信实例教程

150 阅读4分钟

props父子组件相互通信

props是使用频率最高的通信方式,可以实现父子相互通信,子传父是通过父传过来一个方法实现的

App.vue

<template>
 <div>
  <Father> </Father>
 </div>
</template>

Father.vue
<template>
    <div class="father">
        <h1>Father</h1>
        <div>汽车:{{car}}</div>
        <div v-if="toy">收到子的toy:{{toy}}</div>
        <Child :car="car" :sendToy="getToy"></Child>
    </div>
</template>

<script setup lang="ts">
import Child from '@/compnents/Props/Child.vue';
import { ref } from 'vue';
const car = ref('奔驰');
const toy = ref('');
function getToy(value) {
    console.log('父获取到子的toy:',value)
    toy.value = value;
}

</script>

<style scoped>
.father {
    background-color: bisque;
    box-shadow: 0 0 10px;
    padding: 10px;
}
</style>

Child.vue
<template>
    <div class="child">
        <h2>this is child</h2>
        <div>玩具:{{toy}}</div>
        <h3>获取到父亲的车:{{car}}</h3>
        <button @click="sendToy(toy)">告诉父亲自己的爱好</button>
    </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
const toy = ref('奥特曼');
defineProps(["car", "sendToy"])
</script>

<style scoped>
.child {
    background-color: green;
    box-shadow: 0 0 10px;
    color: white;
    padding: 20px;
    margin-top: 10px;
}
</style>

自定义事件

了解$event对象

<script setup>
import Parent from '@/components/Parent.vue'
function action(a,b) {
  console.log('action:', a,b);
}
</script>
<template>
  <!-- 通过点击给方法传值 -->
  <Parent @click="action(1)"></Parent>
  <!-- 这里不传值,则事件收到的第一个参数内容是PointerEvent对象, .target就是html标签对象 -->
  <Parent @click="action"></Parent>
  <!-- 如果想传值并接收点击事件对象,$event就是点击对象 -->
  <Parent @click="action($event, 12)"></Parent>
  
</template>

ref定义出来的响应式数据在模板中不需要.value去获取

自定义事件 数据子传父

示例:子组件传toy给父组件

// Parent.vue
<template>
    <div class="box">
        this is parent
        <h1>收到子传过来的toy: {{toy}}</h1>
        <!-- 父组件监听子组件自定义方法 -->
        <child @xxx="getToyCallback"></child>
    </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import Child from './Child.vue'
const toy = ref('')
// 子组件传过来的toy,父组件接收
function getToyCallback(value) {
    console.log('父组件收到子传过来的toy', value)
    toy.value = value
}
</script>

// Child.vue
<template>
    <div class="box">
        this is child
        <h1>{{toy}}</h1>
        <button @click="emits('xxx', toy)">发送toy给parent</button>
    </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
const toy = ref('熊猫')
const emits = defineEmits(['xxx'])
</script>

自定义方法名称规范:aaa-bbb-ccc的肉串方式kebab-case

mitt任意组件通信

安装 npm i mitt

基础使用

import mitt from 'mitt'

const emitter = mitt()

emitter.on('foo', (e) => {
    console.log(e)
})

setInterval(() => {
    emitter.emit('foo', 'bar')
}, 2000) 

setTimeout(() => {
    // emitter.off('foo')
    emitter.all.clear()
}, 6000);

示例:一个Parent组件俩Child组件相互通信

创建文件src/utils/emitter.ts

import mitt from 'mitt';
export default mitt(); 

Parent.vue

<template>
    <div class="box">
        this is parent
        <h1>收到子传过来的toy: {{toy}}</h1>
        <child></child>
        <child2></child2>
    </div>
</template>

<script setup lang="ts">
import { onUnmounted, ref } from 'vue';
import Child from './Child.vue'
import Child2 from './Child2.vue'
import emitter from '@/utils/emitter'

let toy = ref('')
emitter.on('get-toy', (e:string) => {
    toy.value = e
})

onUnmounted(() => {
    emitter.off('get-toy')
})
</script>

Child.vue

<template>
    <div class="box">
        this is child
        <h1>{{toy}}</h1>
        <h2 v-if="getToy">收到child2传来的toy:{{getToy}}</h2>
        <button @click="emitter.emit('get-toy', toy)">发送toy给parent</button>
    </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import emitter from '@/utils/emitter'
import { onUnmounted } from 'vue'
const toy = ref('熊猫')
const getToy = ref('')
emitter.on('get-toy', (val: string) => {
    getToy.value = val
})

onUnmounted(() => {
    emitter.off('get-toy')
})
</script>

<style scoped>

</style>

Child2.vue

<template>
    <div class="box">
        this is child2
        <h1>{{toy}}</h1>
        <button @click="emitter.emit('get-toy', toy)">发送toy给parent & Child</button>
    </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import emitter from '@/utils/emitter'
const toy = ref('布谷鸟')
</script>

<style scoped>

</style>

v-model

v-model用在html标签上的本质

<template>
    <div>
        <!-- v-model用在html标签上 -->
        <!-- <input type="text" v-model="name"> -->
        <!-- 本质如下 -->
        <input type="text" :value="name" @input="name = (<HTMLInputElement>$event.target).value">
    </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
const name = ref('张三')

</script>

v-mode用在组件标签上

自定义一个输入框LichInput.vue组件

<template>
    <div>
        <input type="text">
    </div>
</template>

<script setup lang="ts">

</script>

<style scoped>

input {
    border: 2px solid black;
    background-image: linear-gradient(45deg, #606dbc, #9678f2, #ff6b81);
    color: white;
    height: 30px;
    font-size: 20px;
}

</style>

App.vue中使用自定义组件

<script setup>
import { ref } from 'vue'
import LichInput from '@/components/LichInput.vue'
const name = ref('张三')
</script>
<template>
 <LichInput v-model="name"></LichInput>
</template>

但是这里写的v-model不会实现双向绑定,无任何作用

所以自定义组件如何实现双向数据绑定,需要了解其本质

<script setup>
import { ref } from 'vue'
import LichInput from '@/components/LichInput.vue'
const name = ref('张三')
</script>
<template>
 <!-- 先手搓一个input -->
 <!-- <LichInput :value="name" @update:value="name = $event"></LichInput> -->
  <!-- 自定义组件的v-mode本质如下 -->
 <!-- <LichInput :modelValue="name" @update:modelValue="name = $event"></LichInput> -->
  <LichInput v-model="name"></LichInput>
  <!-- $event是什么?什么时候能.target -->
   <!--对于html原生事件,$event是事件对象 -->
   <!-- 对于自定义事件,$event是触发事件时,所传递的数据 -->
</template>

自定义组件
<template>
    <div>
        <input type="text" :value="modelValue" @input="emits('update:modelValue',(<HTMLInputElement>$event.target).value)">
    </div>
</template>

<script setup lang="ts">
defineProps(["modelValue"])
const emits = defineEmits(["update:modelValue"])
</script>

<!-- 可以更改value名,那么可以在组件上多次使用v-mode -->
<LichInput v-model:n="name" v-model:pwd="password"></LichInput>
<!-- 自定义组件需要修改为如下内容 -->
<template>
    <div>
        <input type="text" :value="n" @input="emits('update:n',(<HTMLInputElement>$event.target).value)">
            <br>
        <input type="text" :value="pwd" @input="emits('update:pwd',(<HTMLInputElement>$event.target).value)">
    </div>
</template>

<script setup lang="ts">
defineProps(["n","pwd"])
const emits = defineEmits(["update:n","update:pwd"])
</script>

attrs

父组件通过props传给子组件的数据,如果子组件没有通过defineProps接收,那么会将值放到attrs中,如果接收了会放到props中

image-20250824092233195转存失败,建议直接上传图片文件

Parent.vue

<template>
    <div class="box">
        this is parent, v-bind写个对象,等同于 :p3="p3" 
        <div>p1 = {{p1}}</div>
        <div>p2 = {{p2}}</div>
        <div>p3 = {{p3}}</div>
        <child :p1="p1" :p2="p2" v-bind="{p3:p3}"></child>
    </div>
</template>

<script setup lang="ts">
import { onUnmounted, ref } from 'vue';
import Child from './Child.vue'
const p1 = ref(1)
const p2 = ref(2)
const p3 = ref(3)
 
</script>

Child.vue

<template>
    <div class="box">
        收到父组件传过来的值:p1 = {{ p1 }}  
        <div>props:{{$props}}</div>
        <div>attrs{{$attrs}}</div>
    </div>
</template>

<script setup lang="ts">
defineProps(['p1'])
</script>

通过以上内容,我们可以将父组件数据传给子组件,子组件通过attrs透传给孙组件.

只要子组件不通过defineProps接收父组件传过来的数据,那么将会存到$attrs中,通过如下方式透传

<grand-child v-bind="$attrs"></grand-child>

孙组件如何更新父组件

// 父组件定义和传递updateP1方法
<child :p1="p1" :p2="p2" v-bind="{p3:p3}" :updateP1="updateP1"></child>

function updateP1(value) {
    p1.value += value
}

// 孙组件
<template>
    <div class="box">
          grand p2: {{ p2 }} p3: {{ p3 }}
          <button @click="updateP1(3)">updateP1</button>
    </div>
</template>

<script setup lang="ts">
defineProps(['p2', 'p3', 'updateP1'])
</script>

refs & parent

父组件下有俩子组件,父组件怎么修改子组件数据

子组件通过defineExpose暴漏属性让父组件可以修改,父组件通过ref拿到子组件对象进行修改

示例:parent.vue

<template>
    <div class="box">
      book:{{book}}
      <button @click="changeName">child改名</button>
      <Child ref="child"></Child>
      <Child2></Child2>
    </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import Child from './Child.vue'
import Child2 from './Child2.vue'
const book = ref(1)
// 获取到child对象
const child = ref()
// 修改child数据
function changeName() {
    child.value.name = '西门吹雪'
}
</script>

Child.vue

<template>
    <div class="box">
        name: {{ name }}
    </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
let name = ref("小明")
// 数据暴漏后父组件才能修改
defineExpose({
    name
})
</script>

使用$refs批量需改child的名字

// 首先标签先配置ref
<Child ref="child"></Child>
<Child2 ref="child2"></Child2>
// 将$refs传给方法
<button @click="changeAllName($refs)">批量给child改名</button>
// 修改姓名
function changeAllName(refs) {
    for (const key in refs) {
       refs[key].name = '西门吹雪'
    }
}

child修复父组件的数据

// 首先父组件暴漏出数据
defineExpose({book})
// 子组件标签传$parent给方法获取到parent组件
<button @click="changeParent($parent)">给老爹新增2本书</button>
// 修改数据
function changeParent(parent) {
    parent.book += 2
}

provide & inject

准备父子孙三个组件

// Parent.vue
<template>
    <div class="box">
        <h2>父组件</h2>
        <h4>银子:{{money}} 万元</h4>
      <Child ref="child"></Child>
    </div>
</template>

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

let money = ref(10)
</script>

// Child.vue
<template>
    <div class="box">
        子组件
        <GrandChild></GrandChild>
    </div>
</template>

<script setup lang="ts">
import GrandChild from './GrandChild.vue'
</script>

// GrandChild.vue
<template>
    <div class="box">
        <h3>grandchild</h3>
    </div>
</template>

提供数据

// 父组件需要传递的数据provide提供出去
provide('money', money)
// 孙组件接收数据并展示
const money = inject("money")
<h4>{{money}}</h4>
// 父组件有car数据传给孙组件
<script setup lang="ts">
import { provide, reactive } from 'vue';
import Child from './Child.vue'

let car = reactive({
    name: '奔驰',
    price: 100,
})
// 向后代提供数据
provide('car', car)

</script>

// 孙组件:
<template>
    <div class="box">
        <h3>grand child</h3>
        <!-- 这里会提示car找不到name,主要是类型判断导致, 通过给inject添加默认值来解决类型判断问题 -->
        <h4>父组件传来的车:{{car.name}}</h4>
    </div>
</template>

<script setup lang="ts">
import { inject } from 'vue';

const car = inject("car")

// 第二个参数是默认值,通过默认值推断出类型
const car = inject("car", {name: "默认车", price: 10000})
</script>

子传父:父传一个函数作为参数,子收到之后调用函数

// 父组件,传递修改汽车名的函数给子组件
function changeCarName(newName: string) {
    car.name = newName;
}
// 向后代提供数据
provide('carContext', {car, changeCarName})

// 孙组件,通过解构的方式拿到car对象和修改car名字的方法
const {car, changeCarName} = inject("carContext", {car:{name: "默认车", price: 10000}, changeCarName: (name:string) => {} })
// 点击按钮修改车名称
<button @click="changeCarName('雅迪')">修改车名</button>