一个组件要想接受来自父组件的参数,需要显式声明。在使用
<script setup>
的单文件组件中,props 可以使用defineProps()
宏来声明。
一、defineProps的声明:
第一种:仅声明
最简单的声明方式,不指定类型已经默认值。
defineProps(['name','age'])
第二种:带类型的声明方式。
可声明接受的参数的类型,如果与预期的不符合,会在浏览器控制台上发出警告信息。
defineProps({
name: String,
age: String
})
第三种:带类型,带默认值的声明方式。
可以定义一个默认值,当父组件没有传值的时候参数使用该默认值。
defineProps({
name: {
type: String,
default: '李四'
},
age: {
type: Number,
default: 20
}
})
第四种:使用lang="ts"的
当父组件,子组件都使用lang="ts"时,开发者工具会提示开发人员类型是否错误,参数是否传递,这有效的避免开发过程中因类型问题导致的一系列问题。
带问号的参数是可选参数。
<script setup lang="ts">
defineProps<{
name: string,
age?: number
}>();
</script>
二、defineProps的使用
2.1、父传值用法
普通的传值按照标签元素的写法进行传值:
<template>
<Zi name="李四" age="20"/>
</template>
<script setup lang="ts">
import Zi from './Zi.vue'
</script>
传递的参数也可以绑定一个动态值,使用v-bind,简写为冒号,如下:
<template>
<Zi v-bind:name="name" :age="age" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import Zi from './Zi.vue'
const name = ref('李四');
const age = ref(18);
</script>
使用一个对象绑定多个 prop
这与元素的绑定的用法类似,不写name,age属性,只需要一个v-bind来绑定这个对象即可,对象包含了name,age属性:
ps: 当子组件要求age的类型为Number的时候,普通的传值方法age="16"其实无法满足Number的类型要求。只能使用v-bind:age="16"的写法。
其他类型,如Boolean,Array ,List同理,需要使用动态传值的方法。
2.2、子接受值的用法
在模版中直接使用,已经自动解包。
<template>
<div>
{{ '名字:' + name }}
<br>
{{ '年龄' + age }}
</div>
</template>
<script setup>
defineProps(['name','age'])
</script>
在脚本中使用,需要用一个变量或常量接受defineProps的返回值,然后使用该常量来调用参数。
<template>
<div>
{{ '名字:' + name }}
<br>
{{ '年龄' + age }}
</div>
</template>
<script setup>
const props = defineProps(['name','age'])
console.log(props.name);
console.log(props.age);
</script>
三、defineProps传值规则
3.1、单向数据流
所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解。
另外,每次父组件更新后,所有的子组件中的 props 都会被更新到最新值,这意味着你不应该在子组件中去更改一个 prop。若你这么做了,Vue 会在控制台上向你抛出警告:
const props = defineProps(['foo'])
// ❌ 警告!prop 是只读的!
props.foo = 'bar'
当我们想要修改的props的时候,不如将其作为初始值,定义一个新的响应式变量。
const props = defineProps(['initialCounter'])
// 计数器只是将 props.initialCounter 作为初始值
// 像下面这样做就使 prop 和后续更新无关了
const counter = ref(props.initialCounter)
ps: 对于数组或对象的porps来说,由于是引用类型的对象,我们无法修改其引用,但大概率是能修改其内部值。而对 Vue 来说,禁止这样的改动,虽然可能生效,但有很大的性能损耗,比较得不偿失。
大多数需要修改父组件的状态的时候,应该抛出一个事件来通知父组件做出改变!!
3.2、Prop 校验
Vue 组件可以更细致地声明对传入的 props 的校验要求。比如我们上面已经看到过的类型声明,如果传入的值不满足类型要求,Vue 会在浏览器控制台中抛出警告来提醒使用者。这在开发给其他开发者使用的组件时非常有用。
要声明对 props 的校验,你可以向 defineProps()
宏提供一个带有 props 校验选项的对象,例如:
defineProps({
// 基础类型检查
// (给出 `null` 和 `undefined` 值则会跳过任何类型检查)
propA: Number,
// 多种可能的类型
propB: [String, Number],
// 必传,且为 String 类型
propC: {
type: String,
required: true
},
// Number 类型的默认值
propD: {
type: Number,
default: 100
},
// 对象类型的默认值
propE: {
type: Object,
// 对象或数组的默认值
// 必须从一个工厂函数返回。
// 该函数接收组件所接收到的原始 prop 作为参数。
default(rawProps) {
return { message: 'hello' }
}
},
// 自定义类型校验函数
propF: {
validator(value) {
// The value must match one of these strings
return ['success', 'warning', 'danger'].includes(value)
}
},
// 函数类型的默认值
propG: {
type: Function,
// 不像对象或数组的默认,这不是一个
// 工厂函数。这会是一个用来作为默认值的函数
default() {
return 'Default function'
}
}
})
3.3、boolean类型转换
例如,disabled、readonly这些属性,本身的值为true或false。vue对其有着特别的转换规则:
声明:
defineProps({
disabled: Boolean
})
使用:
<!-- 等同于传入 :disabled="true" -->
<MyComponent disabled />
<!-- 等同于传入 :disabled="false" -->
<MyComponent />
当一个 prop 被声明为允许多种类型时,Boolean
的转换规则也将被应用。然而,当同时允许 String
和 Boolean
时,有一种边缘情况——只有当 Boolean
出现在 String
之前时,Boolean
转换规则才适用:
// disabled 将被转换为 true
defineProps({
disabled: [Boolean, Number]
})
// disabled 将被转换为 true
defineProps({
disabled: [Boolean, String]
})
// disabled 将被转换为 true
defineProps({
disabled: [Number, Boolean]
})
// disabled 将被解析为空字符串 (disabled="")
defineProps({
disabled: [String, Boolean]
})