Vue父子组件通讯和三种子父组件通讯的方式

320 阅读4分钟

组件通讯

组件通讯是指在前端开发中,尤其是在使用像 Vue 这样的框架时,不同组件之间进行数据传递和信息共享的过程。

在一个复杂的应用程序中,通常会将界面拆分成多个独立的组件,每个组件负责特定的功能和显示。然而,这些组件之间往往需要相互协作和交互,以实现整体的业务逻辑和用户体验。

例如,一个父组件可能需要将某些数据传递给子组件,以便子组件能够正确地渲染和执行相应的操作;子组件可能在某些情况下需要将处理后的结果或发生的事件通知给父组件;或者两个没有直接父子关系的组件之间也可能需要共享数据和进行交互。

如这个例子中,input和添加按钮算一个父组件,下面的列表算一个子组件,我们想要让这两个组件中的数据流通起来。

comm.gif

父向子组件通讯

App.vue为父组件,child为子组件

  • 父组件将值v-bind绑定,传给子组件,子组件用defineProps接受

情景如下:

  1. 父组件中需要将input框的值传递给子组件,渲染到页面上。
  2. 因此我们对父组件通过v-model绑定一个双向数据,并对按钮添加点击事件
  3. 在子组件上通过v-bind绑定父组件传来的值
  4. 子组件通过defineProps接收值,注意添加一个watch,只有当值发生变更时,我们才将传递的值加入到list中然后响应式数据重新渲染页面。

App.vue

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

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


</template>

<script setup>
import Child from "./components/child.vue";
import { ref } from "vue";
// const list = ref(['html', 'css', 'js'])
const value = ref('')
const toChild = ref('')
const add = () => {
  // list.value.push(value.value)
  // value.value = ''
   toChild.value = value.value
}
</script>

<style lang="css" scoped></style>

child.vue

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

        </ul>
    </div>
</template>

<script setup>
import { ref, watch } from "vue";
import { defineProps } from 'vue';
const list = ref(['html', 'css', 'js'])
// defineProps({
//     list:{
//         type:Array,
//         default:()=>[]
//     }
// })

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

<style lang="scss" scoped></style>

子向父组件通讯(订阅发布机制)

子父组件通讯,借助发布订阅机制,子组件负责发布事件并携带参数,父组件订阅该事件通过事件

“订阅发布”是一种设计模式,用于实现组件之间的通信和数据传递。

“发布”指的是某个组件或模块触发一个事件,并携带相关的数据。

“订阅”则是其他组件或模块预先注册对特定事件的关注,当发布的事件被触发时,订阅了该事件的组件或模块会接收到通知,并能够获取到发布时携带的数据,从而进行相应的处理和响应。

情景如下:

  1. 孩子组件需要向父组件传递数据,于是通过emits创建了一个add1事件,当点击添加时,就发布事件,并携带数据
  2. 父组件只用订阅这个事件即可(handle函数),触发时就将携带的数据添加进list数组即可

child.vue

<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(['add1']) // 创建一个add事件
const add = () => {
    emits('add1', value.value) // 发布事件
}
</script>

<style lang="css" scoped>

</style>

App.vue

<template>
    <!-- 订阅add1事件 -->
    <Child @add1="handle"></Child>

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

        </ul>
    </div>

</template>

<script setup>
import Child from './components/child2.vue';
import {ref} from 'vue'
const list = ref(['html','css','js'])

const handle = (e)=>{
    list.value.push(e)
}
</script>

<style lang="css" scoped>

</style>

子向父组件通讯(二)

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

同样是子组件需要将数据传递给父组件,但是这种方式下,父组件非常轻松,只需要绑定一下数据。

App.vue

<template>
    <Child v-model:list="list"></Child>

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

        </ul>
    </div>

</template>

<script setup>
import Child from './components/child3.vue';
import {ref} from 'vue'
const list = ref(['html','css','js'])


</script>

<style lang="css" scoped>

</style>

child.vue

<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({
    list:{
        type:Array,
        default:()=>[]
    }
})
const emits = defineEmits(['update:list'])
const add = () => {
//    props.list.push(value.value) // 不建议直接操作父组件给过来的数据

    const arr = props.list
    arr.push(value.value)
    emits('update:list',arr)
}
</script>

<style lang="css" scoped>

</style>

子向父组件通讯(三)

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

在这种情况下也很简单,子组件只需要将需要的数据暴露出来即可。 child.vue

<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(['html','css','js'])
const add = () => {
    list.value.push(value.value)
   
}

defineExpose({list:list})
</script>

<style lang="css" scoped>

</style>

父组件则通过ref接收子组件暴露出来的数据,但是需要注意的是:当代码执行到某一行,子组件可能还没有加载完,那么父组件执行到的那一行数据可能还没加载出来就会得到undefined,因此我们可以加一个三元运算符,判断是否加载完毕,如果加载完毕我们才去读取。

App.vue

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

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

        </ul>
    </div>

</template>

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


const childRef = ref(null);
// onMounted(() => {
//     childRef.list
// })
</script>

<style lang="css" scoped>

</style>
  • 除了三元运算符以外,我们也可以通过生命周期函数来解决问题,而不是硬生生的加一个定时器,待过了一段时间才去加载。