通过上篇文章,我们已经了解了组件的概念,同时留下了一个问题,如何实现父子组件的通信呢,这就需要用到我们的 props,当然本文我们还是使用组合式 API 的代码风格来进行讲解。
一:父传子
父组件想要传递数据给子组件只需要两步:
- 父组件给子组件 绑定属性
- 子组件通过 props 进行接收
由于我们使用的是组合式 API,而且使用了 <script setup>
,所以不能直接在子组件中通过 props 进行接收。需要借助于 defineProps 编译宏命令来接收父组件传递过来的数据,同时这个编译宏命令会返回一个对象,这个对象包含了父组件中传递过来的数据。
所谓编译宏命令其实就是一个标识,当代码进行编译的时候,编译器会将其转换成 props 选项
如果还不了解 script setup
的小伙伴可以先简单理解为,script setup
允许我们在单个<script>
标签中同时去编写组件的响应式数据、计算属性、方法等内容,而不必像选项式 API 一样需要分别写在 data、computed、method 等各个地方,其实对于开发和维护来说是更加友好的。
接下来看一个简单的示例:
首先,我们在父组件中给子组件绑定了静态属性 msg,同时也绑定了一个响应式数据 count
// 父组件 Father.vue
<script setup>
import Son from './Son.vue'
import { ref } from 'vue'
const count = ref(100)
</script>
<template>
<Son msg="hello vue" :count="count"></Son>
</template>
我们可以在子组件的模板中直接对父组件传过来的值进行渲染。
// 子组件 Son.vue
<script setup>
const props = defineProps({
msg: String,
count: Number
})
console.log(props.msg)
</script>
<template>
{{ msg }}
{{ count }}
</template>
<style scoped></style>
可能有小伙伴会注意到我们在定义 props 的时候,使用了键值对的形式。
值表示子组件的这个属性所期望能够接收的类型。值得注意的是,这并不是一种强约束。
在下面的例子中我们在父组件中为子组件绑定了一个类型为数组的变量 count,在子组件中声明 count 的类型是 Number
// 父组件
<script setup>
import Son from './Son.vue'
</script>
<template>
<Son count="[1, 2, 3]"></Son>
</template>
// 子组件
<script setup>
const props = defineProps({
count: Number
})
</script>
<template>
{{ count }}
</template>
通过测试我们发现,虽然在子组件的模板中, count 是可以正常被渲染成一个数组的,但是浏览器会在控制台抛出警告。
defineProps 除了可以校验属性的类型,还可以更细致的声明其他校验。声明更细致的校验的好处是,当我们写了一个组件的时候,别人在使用的时候,就知道在进行组件传值的时候,知道应该传什么类型的,同时如果传错了,浏览器也会抛出警告进行提示。
defineProps({
// 对基础类型进行检查
// (给出 `null` 和 `undefined` 值则会跳过任何类型检查)
propA: Number,
// 表示可能接收的多种类型
propB: [String, Number],
// 必传,且为 Number 类型
propC: {
type: Number,
required: true
},
// 声明类型为 Number,且默认值为 10
propD: {
type: Number,
default: 10
},
// 对象类型的默认值
propE: {
type: Object,
// 注意:对象或数组的默认值, 必须从一个工厂函数返回。
// 该函数接收组件所接收到的原始 prop 作为参数。
default(rawProps) {
return { count: 100 }
}
},
// 自定义类型校验函数
propF: {
validator(value) {
// 传过来的值必须是其中之一
return ['info', 'debug', 'error'].includes(value)
}
}
})
二:子传父
讲完了我们的父传子,在介绍子传父之前,需要先了解一下 props 的单向数据流。
所有的 props 都会遵循单向绑定原则,当父组件为子组件所绑定的属性更新时,会自然地将新的状态向下流往子组件,子组件中的 props 也会更新到最新值。
但是在子组件中不能去修改 props 中的值,如果你不小心修改了,浏览器会抛出警告 [Vue warn] Set operation on key "count" failed: target is readonly.
其实这样规定也很好理解,这样做可以避免子组件意外修改父组件的状态的情况,否则应用的数据流将很容易变得混乱。
既然孩子不能去做这种危险的事情,那如果孩子真的有这个需求,应该怎么办呢,其实道理也很简单,那就只能请求父辈去帮我们做,当父辈收到我们的请求时,自然就由他们去进行修改了。
基本的思想:
- 父组件给子组件标签绑定事件
- 子组件通过 emit 方法触发事件
接下来通过一个示例去演示如何使用:
// 子组件
<script setup>
const props = defineProps({
money: Number
})
// 1. 通过编译器宏声明事件
const emit = defineEmits(['giveMoney'])
// 2. 在子组件中使用 emit 触发事件,同时可以携带参数
const onClick = () => {
emit('giveMoney', 200)
}
</script>
<template>
<button @click="onClick">{{ money }}</button>
</template>
// 父组件
<script setup>
import Son from './Son.vue'
import { ref } from 'vue'
const money = ref(100)
const handleEmit = (val) => {
money.value = val
}
</script>
<template>
// 3. 父组件中对 giveMoney 事件进行监听,并设置事件触发后的处理函数
<Son :money="money" @giveMoney="handleEmit"></Son>
</template>
看完这篇文章之后,相信你已经学会了用 vue 进行开发的时候经常会用到的父子组件通信,props 主要是用于传值,下一篇文章我们继续介绍如何使用插槽来自定义模板内容
为了让大家少走弯路,新秀给大家推荐一些书籍,大部分都是新秀看过,质量有保证,并且还提供了高清带目录的电子版,节省大家寻找书籍的时间,看看目录
如何获取这些资料?
在我的公众号「九号新秀」,回复「开源电子书」,即可获取下载链接哦,以下是公众号二维码