Vue 3 中父子组件之间如何进行相互通信?🔁

973 阅读7分钟

前言

父子组件通信,作为Vue应用开发中不可或缺的一部分,是实现数据流动、事件触发以及组件间互动的关键机制。无论是简单的属性传递,还是复杂的交互逻辑,掌握这一技能对于构建富有层次感和互动性的用户界面至关重要。通过学习如何有效地利用props进行数据下行传递,使用emit触发自定义事件,以及借助ref直接操作子组件。本文将详细探讨Vue 3中的父子组件通信,帮助你深入了解并掌握这些核心概念。

父传子

使用Props传递数据和方法

在Vue中,父组件向子组件传递数据最常用的方法是通过propsprops允许父组件以一种声明式的方式向下传递数据到子组件。这种方式不仅简化了数据流的管理,还确保了组件之间的单向数据绑定,使得状态更易于追踪和调试。

代码模块

App.vue

<script setup>
import ChildComponent from './components/ChildComponent.vue'
import { ref } from 'vue'
const title=ref("来自父组件的消息")
const method =()=>{
    console.log("来自父组件的方法")
}
</script>

<template>
   <div>
   
      <h2>父组件</h2>
       <ChildComponent :title="title" :parentmethod="method" />
   </div>
</template>

<style scoped>

</style>

解析:

  • const title:创建了一个响应式的引用(ref),它的值是“来自父组件的消息”。这个引用可以被传递给子组件,并且如果在父组件中修改了它的值,子组件会自动更新。
  • const method:定义了一个简单的方法,当它被执行时,会在浏览器的控制台输出一条信息。
  • :title="title" 的意思就是:将父组件中的title响应式变量的值绑定到子组件的title prop上。每当父组件中的title发生变化时,子组件也会自动更新其对应的title prop,并反映出最新的值。:parentmethod="method"同理

ChildComponent.vue

<template>
    <div>
      在子组件中: {{props.title}}
      <button @click="props.parentmethod">子组件调用父组件的方法</button>
    </div>
</template>

<script setup>
import {
    defineProps,
    ref
} from 'vue'
const props=defineProps({
    title:{
        type:String,
        required:true
    },
    parentmethod:{
        type:Function,
        required:true
    }

})
</script>

<style  scoped>

</style>
  1. 接收props:子组件通过defineProps声明了它期望从父组件接收两个propstitleparentmethod。根据填写requiredtrue|flase,是否判断必需传入。这两个props都是必需的,并且有type来定义明确的类型。
  2. 显示props:在模板中,子组件使用{{ props.title }}来显示父组件传递过来的标题。
  3. 调用父组件方法:子组件还包含了一个按钮,其点击事件被绑定到了props.parentmethod。这意味着当用户点击按钮时,子组件会触发父组件中定义的方法。由于parentmethod是从父组件传入的一个函数引用,因此它会在父组件的作用域内执行。
  4. 响应性:因为title是一个响应式的prop,如果父组件更新了它的值,子组件也会自动重新渲染以反映最新的标题。

效果:

PixPin_2025-01-07_10-42-09.gif

子传父

使用Emit触发自定义事件

子组件与父组件通信的主要方式之一是通过emit触发自定义事件。当子组件想要通知父组件某个特定事件发生时,它可以调用emit来触发一个命名事件,并可以选择性地传递额外的数据。 App.vue

<script setup>
import ChildComponent from './components/ChildComponent.vue'
import { ref } from 'vue'
const title=ref("来自父组件的消息")
const method =()=>{
    console.log("来自父组件的方法")
}
const messageFromChild=ref("")
const handleChildMessage = (message) => {
    console.log('来自子组件的消息:', message);
    messageFromChild.value=message
    // 在这里可以处理子组件发送的消息
};
</script>

<template>
   <div>
       
      <h2>父组件</h2>
       <ChildComponent :title="title" :parentmethod="method"  @child-message="handleChildMessage"/>
       <p>来自子组件的消息:{{messageFromChild}}</p>
   </div>
</template>

<style scoped>

</style>
  • @child-message="handleEvent" 自定义事件名称 attrs + 处理函数
    • child-message:这是你为子组件定义的一个自定义事件名称。它不是内置的DOM事件(如clickinput等),而是由开发者根据需求命名的。通过这个名称,子组件可以触发特定事件并向父组件传递数据。
    • handleEvent:这是父组件中定义的一个方法,用来处理来自子组件的消息。当你在父组件模板中使用@child-message="handleEvent"时,实际上是在说:“每当子组件触发child-message事件时,请调用handleEvent方法”。

ChildComponent.vue

<template>
    <div>
      在子组件中: {{props.title}}
      <hr>
      <button @click="props.parentmethod">子组件调用父组件的方法</button>
      <hr>
      <button @click="childMessage">子组件给父组件发送数据</button>
    </div>
</template>

<script setup>
import {
    defineProps,
    ref,
    defineEmits,

} from 'vue'
const props=defineProps({
    title:{
        type:String,
        required:true
    },
    parentmethod:{
        type:Function,
        required:true
    }

})
const emit = defineEmits(['child-message'])
const childMessage = () => {
    emit('child-message', '我是子组件');
  };
</script>

<style  scoped>

</style>

解析:

  • const emit = defineEmits(['child-message'])
    • defineEmits:这是Vue Composition API的一部分,用于定义子组件可以触发的自定义事件列表。它返回一个函数emit,该函数可以用来触发这些事件。
    • ['child-message'] :这里是一个字符串数组,列出了所有可以由子组件触发的自定义事件名称。在这个例子中,我们只定义了一个名为child-message的事件,如果有多个事件,可以通过数组的形式添加。
  • const childMessage....
    • emit('child-message', '我是子组件') :调用emit函数来触发一个名为child-message的自定义事件,并传递一个字符串'我是子组件'作为参数。这个消息会被发送给监听了child-message事件的父组件。

效果:

PixPin_2025-01-07_12-21-18.gif

图解:

image.png 当我们实现完子组件向父组件,传递信息后,那么长的帅的读者就要问了?怎么向父组件传递方法呢?

使用ref实现子组件对父组件的传递

创建响应式引用:

大家都知道ref 可以用来创建一个响应式的变量,该变量可以被绑定到模板中的元素或组件上。当这个变量的值发生变化时,视图会自动更新。

  import { ref } from 'vue'
    const count = ref(0)

访问子组件实例:

当你需要直接与子组件进行交互时(比如调用子组件的方法或访问它的属性),可以通过给子组件添加 ref 属性来获取子组件的实例。

代码:

我们可以先看子组件向父组件输出了什么? parent组件:

<template>
    <div>
      <Child ref="comp" />
      <button @click="handlerClick">按钮</button>
    </div>
</template>

<script setup>
import { ref } from 'vue'
import Child from './Child.vue'

const comp = ref(null) // 标记DOM 元素  null 组件没用挂载,DOM 也不在 
const title = ref('hello') // 标记数据
const handlerClick = ()=>{
    console.log(comp,comp.value)
   console.log( comp.value.childName)
   comp.value.someMethod()
}

</script>
<style  scoped>
</style>

child组件:

<template>
    <div>
       Child
    </div>
</template>

<script setup>
import {
    defineProps,
    defineEmits,
    defineExpose,
    ref
} from 'vue'
defineExpose({
    childName:"这是子组件的属性",
    someMethod:()=>{
        console.log('这是子组件的方法')
    }
})
</script>
<style  scoped>

</style>
解析:
  • 重点: <Child ref="comp" />:这里定义了一个子组件Child,并通过ref属性给它指定了一个名为comp的引用。这使得父组件可以在JavaScript中直接访问这个子组件实例,调用子组件的方法和属性。

  • defineExpose({ ... }):这是Vue Composition API的一部分,用来明确哪些属性或方法应该暴露给父组件。在这个例子中,我们暴露了两个成员:

    • childName:一个字符串,作为子组件的一个公开属性。
    • someMethod:一个函数,作为子组件的一个公开方法,可以被父组件调用。
  • 最后在父组件就可以通过 comp.value.,调用方法或者属性。

运行机制
  1. 挂载阶段:当父组件首次渲染时,子组件也会一同被挂载。此时,comp.value会被更新为指向子组件的实例。

  2. 点击按钮:用户点击按钮后,handlerClick函数被执行。由于此时子组件已经挂载完成,因此可以通过comp.value访问到子组件实例,并且可以安全地调用它的公开属性和方法。

  3. 输出结果

    • 控制台首先会打印出comp引用及其当前值(即子组件实例)。
    • 接着会打印出子组件的childName属性值:“这是子组件的属性”。
    • 最后,调用子组件的someMethod方法,在控制台上输出:“这是子组件的方法”。
效果:

PixPin_2025-01-07_12-51-26.gif

总结:

  1. 父传子 - 使用props传递数据和方法:父组件可以通过props将数据和方法传递给子组件,这种方式不仅简化了数据流的管理,还确保了组件之间的单向数据绑定,使得状态更易于追踪和调试。
  2. 子传父 - 使用emit触发自定义事件:子组件可以通过emit触发自定义事件来通知父组件某些特定事件的发生,并可以选择性地传递额外的数据。父组件可以通过监听这些事件来做出相应的反应。
  3. 使用ref直接操作子组件实例:当需要直接与子组件进行交互时,比如调用子组件的方法或访问它的属性,可以通过给子组件添加ref属性来获取子组件的实例。借助defineExpose,可以有选择性地暴露子组件的属性和方法给父组件。