从入门到精通:Vue父子组件通讯的全方位解决方案

344 阅读7分钟

前言

在Vue.js框架中,组件之间的通信是构建复杂应用的关键一环。Vue提供了多种灵活的方式来实现父子组件间的通信,今天我们就来深入探讨实现父子组件通讯和子父组件通讯的几种方法。

就以下面的例子来讲解一下组件通讯。html结构中一共有两个盒子,一个来放输入框,一个来放列表。用v-model双向绑定可以实现在input框输入内容在列表中展示出来,在添加框里面绑定一个add事件,接着在列表中用v-for循环数组list,让数组中的内容展示出来。

<div class="input-group">
    <input type="text" v-model="value">
    <button @click="add">添加</button>
</div>

<div class="child">
    <ul>
        <li v-for="item in list">{{ item }}</li>
    </ul>
</div>

在js结构中,引入ref让内容响应式,定义一个数组list和一个空字符串valuelistvalue都是一个响应式引用,最后在add函数中将输入框的内容放进数组中,value.value = '' 可以让输入框内容清空,以便下一次输入。

import { ref } from 'vue';
const list = ref(['玫瑰','牡丹','月季'])
const value = ref('')
const add = () => {
    list.value.push(value.value)
    value.value = ''
}

动画3.gif

vue组件通讯

父子组件通讯

方法一、Props传递

在这个例子中就是在父组件中存放输入框和数组list,在子组件中存放列表,父组件通过v-bind指令将数据传递给子组件,子组件使用defineProps来定义接收的属性(在这个例子中是属性msg,属性名可以任意更改)。

父组件代码:

<template>
    <div class="input-group">
      <input type="text" v-model="value">
      <button @click="add">添加</button>
    </div>

     <Child :msg="list"></Child>
</template>

<script setup>
import { ref } from 'vue';
import Child from '@/components/child.vue';

// 定义响应式数据
const list = ref(['玫瑰','牡丹','月季']); // 初始化一个包含三个元素的数组
const value = ref(''); // 用于存储输入框的值

// 添加方法,将输入框的值添加到list数组中,并清空输入框
const add = () => {
  list.value.push(value.value);
  value.value = '';
};
</script>

父组件中包含一个输入框和一个按钮,以及一个名为Child的子组件。用户可以在输入框中输入内容,点击按钮后,输入的内容会被添加到一个列表中,这个列表的数据会传递给子组件进行展示。

子组件代码:

<template>
  <div class="child">
    <ul>
      <li v-for="item in msg">{{ item }}</li>
    </ul>
  </div>
</template>

<script setup>
import { ref } from 'vue';

// 使用defineProps定义接收的属性
defineProps({
  msg: {
    type: Array, // 指定msg应该是一个数组
    default: () => [] // 如果没有提供msg,默认为一个空数组
  }
});

子组件接收来自父组件的数据,并根据这个数据动态渲染列表项。它通过defineProps来定义接收的属性(这里是msg),并使用v-for指令遍历这个数组来生成列表。

方法二、父组件向子组件传递数据,子组件监听属性变化作出响应

方法二和方法一不同的地方在于,方法一中是将数组list的数据传给子组件,在这个例子中将数组list直接放在子组件中,父组件只需要将输入框中的值value传给子组件即可。

父组件代码:

<template>
    <div class="input-group">
      <input type="text" v-model="value">
      <button @click="add">添加</button>
    </div>

    <Child :msg="toChild"></Child>
</template>

<script setup>
import { ref } from 'vue';
import Child from '@/components/child.vue'

const value = ref('')
const toChild = ref('')
let add = () => {
  toChild.value = value.value
  value.value = ''
}
</script>

这里的父组件与方法一基本一致,唯一不同的地方就是重新定义了一个变量toChild,这个变量用于暂时存放要传递给子组件的值value,父组件再通过:msg属性将这个变量传给子组件。

子组件代码:

<template>
    <div>
        <div class="child">
        <ul>
            <li v-for="item in list">{{ item }}</li>
        </ul>
        </div>
    </div>
</template>

<script setup>
import { ref, watch } from 'vue';

const list = ref(['玫瑰','牡丹','月季'])

const props = defineProps({
    msg: ''
})
watch(
    () => props.msg,
    (newVal, oldVal) => {
        list.value.push(newVal)
    }
)
</script>

在子组件中通过defineProps接收来自父组件的属性,这里只接收一个名为msg的属性。利用watch函数监听props.msg的变化。每当msg的值发生变化(即父组件传入新值),都会执行提供的回调函数。在这个回调里,新值(newVal)被推送到list数组中,从而实现动态添加列表项的功能。

子父组件通讯

方法一、发布订阅机制

借助发布订阅机制,子组件负责发布事件并携带参数,父组件订阅该事件通过事件参数获取子组件提供的值。

子组件代码示例:

<template>
    <div class="input-group">
      <input type="text" v-model="value">
      <button @click="add">添加</button>
    </div>
</template>

<script setup>
import {ref} from 'vue'
const value = ref('')

const emits = defineEmits(['create']) // 创建一个create事件
const add = () => {
    // 将value给到父组件
    emits('create', value.value)// 发布事件
    value.value = ''
}
</script>

在这个例子中,子组件中存放输入框,父组件中存放列表和数组list。在子组件中通过defineEmits方法创建一个新的事件(在这里是create事件),在add函数中调用emits函数发布事件,emitsdefineEmits方法返回的一个函数。

父组件代码示例:

<template>
    <!-- 订阅create事件 -->
    <Child @create="handle"></Child>
    <div>
        <div class="child">
        <ul>
            <li v-for="item in list">{{ item }}</li>
        </ul>
        </div>
    </div>
</template>

<script setup>
import Child from '@/components/child2.vue'
import {ref} from 'vue'
const list = ref(['玫瑰','牡丹','月季'])
const handle = (event) => {
    list.value.push(event)
}

</script>

在父组件中引用子组件Child,通过@create="handle"订阅子组件发出的create事件,当create事件触发时执行handle方法。event 参数是当 create 事件被触发时,从 Child 组件传递给父组件的数据 value

方法二、v-model语法糖

v-model是一个语法糖,用于在表单控件或自定义组件上创建双向数据绑定。子组件需要提供一个名为update:xxx的事件,其中xxx对应父组件中v-model绑定的属性名。

父组件借助v-model将数据绑定给子组件,子组件创建'update:xxx'事件,并将接收到的数据修改后emits出来。

子组件代码示例:

<template>
    <div class="input-group">
      <input type="text" v-model="value">
      <button @click="add">添加</button>
    </div>
</template>

<script setup>
import {ref} from 'vue'
const value = ref('')
const props = defineProps({
    msg: {
        type: Array,
        default: () => []
    }
})

const emits = defineEmits(['update:msg'])
const add = () => {
    // props.list.push(value.value)// 不建议直接操作父组件给过来的数据
    const arr = props.msg
    arr.push(value.value)
    emits('update:msg', arr)
    value.value = ''
}
</script>
  • 在子组件中,通过 defineProps 方法接收名为 msg 的 属性,类型为数组,默认为空数组。

  • 使用 defineEmits 声明一个名为 'update:msg' 的自定义事件。

  • add 方法在按钮点击时执行,它不直接修改 props.list(直接修改是不推荐的做法,因为这会违反 Vue 的单向数据流原则),而是创建了 props.msg 的一个副本 arr,向这个副本添加新的值,然后通过 emits 触发 'update:msg' 事件,将修改后的数组传回父组件。

父组件代码示例:

<template>
    <Child v-model:msg="list"></Child>
    <div>
        <div class="child">
        <ul>
            <li v-for="item in list">{{ item }}</li>
        </ul>
        </div>
    </div>
</template>

<script setup>
import Child from '@/components/child3.vue'
import {ref} from 'vue'
const list = ref(['玫瑰','牡丹','月季'])
</script>

v-model 绑定:在父组件中,通过v-model:msg="list"实现了对子组件中 msg 属性的双向绑定。这里的关键在于子组件内部正确实现了自定义事件('update:msg'),这使得父组件能监听到子组件对 list 数据的变更请求,并作出相应更新。

方法三、利用defineExpose()暴露子组件数据给父组件

父组件通过ref获取子组件中defineExpose() 暴露出来的数据。

子组件代码示例:

<template>
    <div class="input-group">
      <input type="text" v-model="value">
      <button @click="add">添加</button>
    </div>
</template>

<script setup>
import {ref} from 'vue'
const value = ref('')
const list = ref(['玫瑰','牡丹','月季'])

const add = () => {
    list.value.push(value.value)
    value.value = ''
}

defineExpose({list}) // 心甘情愿暴露list
</script>

通过defineExpose({list}),子组件心甘情愿地将其内部的list暴露给父组件,允许父组件访问这个数据。

父组件代码示例:

<template>
    <Child ref="childRef"></Child>

    <div>
        <div class="child">
        <ul>
            <li v-for="item in childRef?.list">{{ item }}</li>
        </ul>
        </div>
    </div>
</template>

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

const childRef = ref(null)
  • 引入子组件Child并使用ref给它分配了一个引用childRef。在无序列表中,通过v-for遍历childRef?.list来渲染子组件暴露出来的list数据。

  • 使用ref创建childRef,初始值为nullconst childRef = ref(null)是为了确保在子组件Child被挂载并可以被引用之前,childRef有一个确定的初始值,从而避免在访问它的属性之前出现引用错误。

  • 父组件通过创建子组件的引用并利用可选链操作符(?.)安全地访问子组件暴露的list,确保在子组件完全加载和初始化后再进行数据访问,避免了潜在的错误和未定义问题。

结语

今天的分享就到这里啦,希望可以给你带来帮助。

image.png