vue父子组件的通讯方式

366 阅读5分钟

前言

  • 父子组件通讯: 父组件向子组件通讯,父组件将值v-bind绑定传给子组件,子组件使用defineProps来接收父组件给的值。
  • 子父组件通讯:
    • 方式一:借助发布订阅机制,子组件负责发布事件并携带事件参数,父组件订阅该事件通过事件参数获取子组件提供的值。
    • 方式二:父组件借助v-model将数据绑定给子组件,子组件创建'update: xxxx'事件,接收到该数据将修改后的数据emits出来。
    • 方式三:父组件通过ref获取子组件中defineExpose() 暴露出来的数据

假如我们要实现如下效果

add.gif

  • 首先我们需要为li绑定一个v-for,使得list中的数据能够在li中显示出来,li中的数据不能写死,并且还需要处理成响应式数据,才能够在浏览器页面中显示出来。
  • 其次我们需要为输入框绑定一个v-model,实现数据的双向绑定,使得在输入框输入的数据会同步到date这个自定的变量中去。
  • 再为添加按钮绑定一个点击事件,当我们去点击按钮时,输入框中的数据会被添加到list中,并且最后在页面中显示出来。并且点击添加按钮后需要把input框中的数据置为空。就需要data.value = ''
  • 其中list 和 date都必须处理成响应式数据,不然不会在视图中显示更新的数据。
<template>
  <div>
    <div class="input-group">
      <!-- -model 双向绑定,会把输入同步到某个变量中去 -->
      <input type="text" v-model="data">
      <button @click="add">添加</button>
    </div>
    <div class="child">
      <ul>
        <li v-for="item in list">{{ item }}</li>
      </ul>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
const list = ref(['html', 'css', 'js'])
const data = ref('')

const add = () => {
  list.value.push(data.value)
  data.value = ''
}
</script>

<style lang="css" scoped>

</style>

父子组件通讯

在src文件夹创建一个component文件夹,再到component文件夹下创建一个child.vue,将<div class="child">放入child.vue,实现父组件App.vue向子组件child.vue发送数据,子组件接收数据。也能实现上图效果。

App.vue

<template>
    <div class="input-group">
      <!-- -model 双向绑定,会把输入同步到某个变量中去 -->
      <input type="text" v-model="data">
      <button @click="add">添加</button>
    </div>
    <Child v-bind:abc="list"></Child>
</template>

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

import { ref } from 'vue'
const list = ref(['html', 'css', 'js'])
const data = ref('')

const add = () => {
  list.value.push(data.value)
  data.value = ''
}
</script>

<style lang="css" scoped>

</style>
  • 通过import Child from '@/components/child.vue'; <Child v-bind:list="list"></Child>向父组件中导入子组件
  • 再通过v-bind将父组件的list数据属性绑定到子组件的list属性上,使得子组件能够使用父组件的list数据。

child.vue

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

<script setup>
import { defineProps } from 'vue';
defineProps({
    abc: {
        type: Array,
        default: () => []
    }
})
</script>

<style lang="css" scoped>

</style>
  • 子组件通过defineProps来接收父组件的list数据,import { defineProps } from 'vue';这行代码可以不写,因为vue中默认引入了这个方法。

这种方式是将list放在父组件中,并且将input输入框中的值加入到list之后,再将list传给子组件child。

image.png 还有一种方式就是将list中的数据放在子组件child中,然后父组件将input输入框中的值传给子组件。

image.png

App.vue

<template>
    <div class="input-group">
      <!-- -model 双向绑定,会把输入同步到某个变量中去 -->
      <input type="text" v-model="data">
      <button @click="add">添加</button>
    </div>
    <Child v-bind:msg="toChild"></Child>
</template>
<script setup>

import Child from '@/components/child.vue';
import { ref } from 'vue'
const data = ref('')
const toChild = ref('')

const add = () => {
  toChild.value = data.value
}
</script>

<style lang="css" scoped>

</style>
  • <Child v-bind:msg="toChild"></Child>将输入框的值通过v-bind绑定到子组件上。

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';
const list = ref(['html', 'css', 'js'])
const props = defineProps({
    msg: ''
})
watch(
    () => props.msg,
    (newVal,oldVal) => {
        list.value.push(newVal)
    }
)
</script>

<style lang="css" scoped>

</style>
  • 通过defineProps将接收到的值赋给props变量,再通过watch来间监听msg的值的变化,watch默认有返回两个回调函数,第一个回调用于监听msg的变化,第二个回调用于记录msg的新的值和旧的值。
  • 通过list.value.push(newVal)将msg的值push到list中,由于list是响应式的数据,所以li中的v-for在list发生变化时会重新循环list,最后将值渲染到页面上。

子父组件通讯

方式一

child.vue

<template>
    <div class="input-group">
      <!-- -model 双向绑定,会把输入同步到某个变量中去 -->
      <input type="text" v-model="data">
      <button @click="add">添加</button>
    </div>
</template>

<script setup>
import { ref } from 'vue'
const data = ref('')
const emits = defineEmits(['add1'])  // 创建一个add1事件
const add = () => {
    // 将data给父组件
    emits('add1', data.value) // 发布事件
}
</script>

<style lang="scss" scoped>

</style>
  • 将输入框和按钮放入到子组件中,通过defineEmits方法创建一个add1事件赋给变量emits(此时emits是一个函数)
  • 并在add点击事件中调用emits函数,emits('add1', data.value)emits函数接收两个参数,第一个是发布的事件名,第二个是传出去的事件值。

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/child.vue'
import { ref } from 'vue'
const list = ref(['html', 'css', 'js'])

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

<style lang="css" scoped>

</style>
  • 在父组件中绑定一个add1事件,函数名取为handle,handle在点击子组件的按钮时会被触发,因为当点击按钮时,子组件中定义的add1事件就会被发布出去。并且add1在发布的同时还带着一个事件参数event,将该事件参数push到list里面,就能显示相应的效果。

方式二

App.vue

<template>
  <!-- 订阅add1事件  发布订阅事件模式-->
  <Child v-model:abc="list"></Child>
    <div class="child">
      <ul>
        <li v-for="item in list">{{ item }}</li>
      </ul>
    </div>
</template>

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

</script>

<style lang="css" scoped>

</style>
  • 在父组件中使用v-model<child>标签双向绑定list,赋给变量abc,

child.vue

<template>
    <div class="input-group">
      <!-- -model 双向绑定,会把输入同步到某个变量中去 -->
      <input type="text" v-model="data">
      <button @click="add">添加</button>
    </div>
</template>

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

const props = defineProps({
    abc: {
        type: Array,
        default: () => []
    }
})
const emits = defineEmits(['update:abc'])
const add = () => {
    // props.abc.push(data.value)   // 不建议直接操作父组件给过来的数据
    const arr = props.abc
    arr.push(data.value)
    data.value = ''
    emits('update:abc', arr)
}

</script>

<style lang="scss" scoped>

</style>
  • 先通过defineEmits接收一个数组参数,发布一个名为update:abc事件,用于更新父组件中abc的值。
  • 当触发点击事件时,可以通过 props.abc.push(data.value)来将更新的值push到abc中去,但是不建议这样写,因为这样相当于直接操作了父组件传过来的数据。
  • 于是通过const arr = props.abc把abc赋给arr,直接把arr更新到abc中去。又因为在父组件中使用了v-model双向绑定了abc的值,所以不需要在父组件中绑定update:abc事件。

方式三

child.vue

<template>
    <div class="input-group">
      <!-- -model 双向绑定,会把输入同步到某个变量中去 -->
      <input type="text" v-model="data">
      <button @click="add">添加</button>
    </div>
</template>

<script setup>
import { ref } from 'vue'
const data = ref('')
const list = ref(['html', 'css', 'js'])

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

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

</script>

<style lang="css" scoped>

</style>
  • defineExpose()将list属性暴露出来给父组件

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/child.vue'
import { ref } from 'vue'

const childRef  =ref(null)
</script>

<style lang="css" scoped>

</style>
  • 通过ref获取子组件暴露出来的属性list,赋给childRef,childRef?.list是指childRef有值就执行v-for来遍历该list,如果childRef还没拿到子组件传来的数据,childRef?.list就会返回undefined。

结语

qiuzan.jpg