vue父子组件通讯的四种方法,看完包会
引言
本文将介绍父组件与子组件之间传递数据的四种方法,以一个简单的小demo为例,通过实例全方位解析和代码演示,便于大家理解
demo效果
父子组件通讯
父组件展示输入框用于新增数据,子组件展示数组信息
场景一:直接传递一整个数组
父组件在子组件标签中使用v-bind
绑定一个属性名为list
,属性值为要传递的数组list
子组件调用函数defineProps
,接收父组件传过来的数据。defineProps
函数是vue默认帮我们引入好了的,它默认接收一个对象作为参数,该对象包含一个字段,字段名为父组件绑定的属性名list
,字段值又为一个对象,对象中包含子组件期望接收到的数据类型type
和默认值default
,此处期望接收到一个数组,默认值为一个空数组。list
可以直接拿到template模块中使用
父组件代码:
<template>
<div class="inputGroup">
<input type="text" v-model="value">
<button @click="add">添加</button>
</div>
<Child :list="list"> </Child> // 父组件将值v-bind绑定传给子组件
</template>
<script setup>
import Child from '@/components/child.vue' // 引入子组件
import { ref } from 'vue'
const value = ref('')
const list = ref(['html', 'css', 'js'])
const add = () => {
list.value.push(value.value)
value.value = ''
}
</script>
子组件代码:
<template>
<div class="child">
<ul>
<li v-for="item in list">{{ item }}</li>
</ul>
</div>
</template>
<script setup>
defineProps({ // 子组件使用defineProps接收
list: {
type: Array,
required: true,
default: () => []
}
})
</script>
这个通讯过程本身就是一个响应式的过程,所以父组件向子组件传递过去的属性值 list 发生改变后,子组件会重新接收一遍最新的值,由于 list 被定义成了响应式,浏览器最终就会将新添加的值成功渲染出来
场景二:只传递新增加的那个值
父组件在子组件标签中使用v-bind
绑定一个属性名为msg
,属性值为要传递的新增数据toChild
子组件定义一个变量props
接收defineProps
函数执行的结果,再将接收到的新增数据
props.msg
添加进子组件已经定义好的list
数组中
父组件代码:
<template>
<div class="inputGroup">
<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 value = ref('')
const toChild = ref('')
const add = () => {
toChild.value = value.value
}
</script>
子组件代码:
<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: '' // 直接简写,不再把msg对应的值写成一个对象
})
watch( // 监视props.msg的值的变化,一旦变化执行回调函数
() => props.msg,
(newVal, oldVal) => {
list.value.push(newVal)
}
)
</script>
defineProps
函数中的字段可以直接被拿到template中使用,但如果要在js脚本中使用,需要一个变量来接收这个函数的执行结果
注意:这里不直接
list.value.push(props.msg)
,而是需要watch
对props.msg
的值进行监听是因为:list的更新需要list.value.push()
这句js代码反复去执行,而js代码在浏览器第一遍渲染页面完成后,不会再执行第二遍,所以需要watch函数在监视到值变化后,主动去执行list的更新
子父组件通讯一
父组件展示数组信息,子组件展示输入框用于新增数据
借助发布订阅机制,子组件调用defineEmits
函数接受一个数组作为参数,数组 [ 'new' ]
表示组件可以触发一个名为 'new'
的自定义事件,返回给emits
对象,可以用它来触发 new
事件。点击按钮后,调用emits
函数发布事件,传递的参数分别为要传输给父组件的事件名new
和事件值value.value
父组件订阅该事件,通过事件参数获取子组件提供的值
父组件代码:
<template>
<!-- 订阅new事件-->
<Child @new="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 = (event) => { // event事件参数,其实就是子组件发布事件时传输过来的值
list.value.push(event)
}
</script>
子组件代码:
<template>
<div class="inputGroup">
<input type="text" v-model="value">
<button @click="add">添加</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const value = ref('')
const emits = defineEmits(['new']) // 创建一个new事件
const add = () => {
emits('new', value.value) // 发布事件
}
</script>
子父组件通讯二
仍然是父组件展示数组信息,子组件展示输入框用于新增数据
能用但不建议版
如果将list
数组看做成篮子,新增数据看做成苹果,那么子父组件通讯一就是儿子把苹果丢给父亲,父亲再将苹果装入篮中;子父组件通讯二就是父亲把篮子共享给了儿子,儿子将苹果装入篮中
父组件定义了list
数组,通过v-model:list
指令将父组件的 list
属性与子组件中的 list
prop 进行了双向绑定,意味着当在子组件内部修改 list 时,这些更改也会反映回父组件的 list 属性中
子组件接收list的方式就是使用defineProps
接收
父组件代码:
<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>
子组件代码:
<template>
<div class="inputGroup">
<input type="text" v-model="value">
<button @click="add">添加</button>
</div>
</template>
<script setup>
import { ref, defineProps } from 'vue'
const value = ref('')
const props = defineProps({
list: {
type: Array,
default: () => []
}
})
const add = () => {
props.list.push(value.value)
}
</script>
但是vue官方不建议我们让子组件直接操作父组件给过来的数据,因为这样会导致数据流很混乱。正常来讲我自己的数组想要被修改,就应该由我自己来改,而不是交到别人手上去改
优化版
所以子组件的js代码应当优化成下面的样子
使用 defineEmits
函数来声明组件可以触发的事件update:list
,在 Vue 中,以 update:
开头的事件通常用于通知父组件 子组件内部数据的变更。arr
接收list
prop 的引用,再把更新过后的arr
抛出出去,v-model:list
就会自动get到最新的值
<script setup>
import { ref, defineProps } from 'vue'
const value = ref('')
const props = defineProps({
list: {
type: Array,
default: () => []
}
})
const emits = defineEmits(['update:list'])
const add = () => {
const arr = props.list
arr.push(value.value)
emits('update:list', arr)
}
</script>
子父组件通讯三
仍然是父组件展示数组信息,子组件展示输入框用于新增数据
父组件直接读取到子组件更新后的list
:父组件中定义了一个响应式变量childRef
,childRef
作为一个标记打到子组件标签上,就可以通过childRef获取到子组件的任何数据
子组件自己完成对list的更新,调用defineExpose
函数,指定list
的数据可以被外部访问
父组件代码:
<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, onMounted } from 'vue'
const childRef = ref(null)
</script>
子组件代码:
<template>
<div class="inputGroup">
<input type="text" v-model="value">
<button @click="add">添加</button>
</div>
</template>
<script setup>
import { ref, defineProps } from 'vue'
const value = ref('')
const list = ref(['html', 'css', 'js'])
const add = () => {
list.value.push(value.value)
}
defineExpose({ list }) // 自愿暴露数据
</script>
ref
是 Vue 中的一个特殊属性,它允许我们在父组件中引用子组件或 DOM 元素
item in childRef?.list
中,?
是ES6的新语法,当childRef有值的时候才会读取后面的.list,没有的时候就不会去读取 (这样安排是因为父组件在执行这行代码的时候,子组件可能还未加载完毕,这样以免报错)
总结
- 父子组件通讯:父组件将值
v-bind
绑定传给子组件,子组件使用defineProps
接收 - 子组件向父组件通讯:借助发布订阅机制,子组件负责发布事件并携带参数,父组件订阅该事件,通过事件参数获取子组件提供的值
- 子组件向父组件通讯:父组件借助
v-model
将数据绑定给子组件,子组件创建'update:xxxx'
事件,并将接收到的数据修改后emits
出来 - 子组件向父组件通讯:父组件通过
ref
获取子组件中defineExpose()
暴露出来的数据
认真读完后相信你已经对父组件与子组件的信息交互有了更加深刻的理解,阅读过程中若发现有任何不足或亮点,欢迎大家在评论区指出交流,我们共同进步~
转载自:https://juejin.cn/post/7395106706819072038