Appearance
Props
Props 声明
一个组件需要显示的声明它所接收的props,这样Vue才能知道如何正确地解析它们
vue
<script setup>
const props = defineProps(['foo'])
console.log(props.foo)
</script>javascript
export default {
props: ['foo'],
setup(props) {
// setup() 接收 props 作为第一个参数
console.log(props.foo)
},
}除了使用字符串以外,还可以使用对象的形式
javascript
// 使用 <script setup>
defineProps({
title: String,
likes: Number,
})
// 使用 <script>
// 非 <script setup>
export default {
props: {
title: String,
likes: Number,
},
}对于对象形式的声明,key时props的名称,而值时与其类型的构造函数
响应式Props结构 (3.5+)
在计算属性或侦听函数中访问props中的属性时,被访问的属性会被跟踪为依赖项
javascript
const { foo } = defineProps(['foo'])
watchEffect(() => {
// 在 3.5 之前只运行一次
// 在 3.5+ 中在 "foo" prop 变化时重新执行
console.log(foo)
})在 3.4 及以下版本中,foo是一个常量,永远不会改变.而 3.5+ 版本中,foo是一个响应式引用,当foo变化时,watchEffect会重新运行, 上面的代码等价于
javascript
const props = defineProps(['foo'])
watchEffect(() => {
// `foo` 由编译器转换为 `props.foo`
console.log(props.foo)
})此外,还可以使用给JavaScript原生的默认值语法声明props默认值
javascript
const {foo = 'hello'} = defineProps < {foo? : string} > ()将结构的props传递到函数中
javascript
const { foo } = defineProps(['foo'])
watch(foo /* ... */)这里并不会按照预期执行,因为它等价与watch(props.foo,...),而props.foo是一个常量,永远不会改变,这里可以将其包装在 getter中来侦听结构的props
javascript
watch(() => foo /* ... */)传递prop的细节
名字格式
如果名字很长,应该使用cameCase形式,即使合法的JavaScript标识符,也可以直接在模板的表达式中使用,还可以避免作为属性key 时必须添加的引号
javascript
defineProps({
greetingMessage: String,
})理论上,在向子组件传递props时,camelCase和kebab-case都可以使用,但为了和HTML attribute对其,推荐写成kebab-case的形式
html
<MyComponent greeting-message="hello" />静态 vs 动态
可以使用v-bind或:来动态的绑定props
html
<!-- 根据一个变量的值动态传入 -->
<BlogPost :title="post.title" />
<!-- 根据一个更复杂表达式的值动态传入 -->
<BlogPost :title="post.title + ' by ' + post.author.name" />传递不同的值类型
任何类型的值都可以作为props的值被传递
html
<!-- 虽然 `42` 是个常量,我们还是需要使用 v-bind -->
<!-- 因为这是一个 JavaScript 表达式而不是一个字符串 -->
<BlogPost :likes="42" />
<!-- 根据一个变量的值动态传入 -->
<BlogPost :likes="post.likes" />
<!-- 仅写上 prop 但不传值,会隐式转换为 `true` -->
<BlogPost is-published />
<!-- 虽然 `false` 是静态的值,我们还是需要使用 v-bind -->
<!-- 因为这是一个 JavaScript 表达式而不是一个字符串 -->
<BlogPost :is-published="false" />
<!-- 根据一个变量的值动态传入 -->
<BlogPost :is-published="post.isPublished" />
<!-- 虽然这个数组是个常量,我们还是需要使用 v-bind -->
<!-- 因为这是一个 JavaScript 表达式而不是一个字符串 -->
<BlogPost :comment-ids="[234, 266, 273]" />
<!-- 根据一个变量的值动态传入 -->
<BlogPost :comment-ids="post.commentIds" />
<!-- 虽然这个对象字面量是个常量,我们还是需要使用 v-bind -->
<!-- 因为这是一个 JavaScript 表达式而不是一个字符串 -->
<BlogPost
:author="{
name: 'Veronica',
company: 'Veridian Dynamics'
}"
/>
<!-- 根据一个变量的值动态传入 -->
<BlogPost :author="post.author" />使用对象绑定多个 prop
如果想将一个对象的所有属性都作为props传入,可以使用没有参数的v-bind
javascript
const post = {
id: 1,
title: 'My Journey with Vue',
}html
<BlogPost v-bind="post" />这等价于:
html
<BlogPost :id="post.id" :title="post.title" />单项数据流
所有的props都遵循着单向数据流,即props会因为父组件中状态的改变将新的状态传递给子组件,但子组件不能改变父组件中的状态, 因为子组件接收的props会随着父组件的状态更新,所以子组件中不应该去修改prop
导致想修改prop的需求通常时以下两种场景:
1.子组件想将prop作为局部数据使用
javascript
const props = defineProps(['initialCounter'])
// 计数器只是将 props.initialCounter 作为初始值
// 像下面这样做就使 prop 和后续更新无关了
const counter = ref(props.initialCounter)2.子组件想将接收到的porp进一步转换
javascript
const props = defineProps(['size'])
// 该 prop 变更时计算属性也会自动更新
const normalizedSize = computed(() => props.size.trim().toLowerCase())总之,在子组件中不应该直接引用prop进行使用
更改对象/数组类型的props
当对象或数组作为props被传入时,虽然子组件不能修改props的绑定,当是可以修改其内部的值,因为JavaScript的对象和数组时按引用传递, 为了保证单项数据流的规定,应该避免直接修改props的值,而是应该将props 的值赋值给一个本地的数据,然后修改本地的数据,如果需要父组件对数据改变做出回应,子组件应该抛出一个事件来通知父组件
Props 校验
javascript
defineProps({
// 基础类型检查
// (给出 `null` 和 `undefined` 值则会跳过任何类型检查)
propA: Number,
// 多种可能的类型
propB: [String, Number],
// 必传,且为 String 类型
propC: {
type: String,
required: true,
},
// 必传但可为 null 的字符串
propD: {
type: [String, null],
required: true,
},
// Number 类型的默认值
propE: {
type: Number,
default: 100,
},
// 对象类型的默认值
propF: {
type: Object,
// 对象或数组的默认值
// 必须从一个工厂函数返回。
// 该函数接收组件所接收到的原始 prop 作为参数。
default(rawProps) {
return { message: 'hello' }
},
},
// 自定义类型校验函数
// 在 3.4+ 中完整的 props 作为第二个参数传入
propG: {
validator(value, props) {
// The value must match one of these strings
return ['success', 'warning', 'danger'].includes(value)
},
},
// 函数类型的默认值
propH: {
type: Function,
// 不像对象或数组的默认,这不是一个
// 工厂函数。这会是一个用来作为默认值的函数
default() {
return 'Default function'
},
},
})一些补充细节:
- 所有的
prop默认都是可选的,除非声明了required:true - 除了
Boolean外的为传递的可选prop都有一个默认值undefined Boolean类型的为传递prop将会被转换为false但是可以通过设置default来更改,比如设置为default:undefined- 如果声明了
default值,那么无论是未传递还是显示的指明为undefined,都会改为default的值
当prop校验失败会,会在开发者模式下的控制台抛出警告
运行时类型检查
校验选项中的type可以时下列这些原生构造函数
StringNumberBooleanArrayObjectDateFunctionSymbol
另外type也可以是自定义的类或构造函数
javascript
class Person {
constructor(firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
}
defineProps({
author: Person,
})Boolean 类型转换
声明为Boolean类型的props有特别的类型转换规则 :
javascript
defineProps({
disabled: Boolean,
})html
<!-- 等同于传入 :disabled="true" -->
<MyComponent disabled />
<!-- 等同于传入 :disabled="false" -->
<MyComponent />当一个 prop 被声明为允许多种类型时,Boolean 的转换规则也将被应用。然而,当同时允许 String 和 Boolean 时,有一种边缘情况——只有当 Boolean 出现在 String 之前时,Boolean 转换规则才适用:
javascript
// disabled 将被转换为 true
defineProps({
disabled: [Boolean, Number],
})
// disabled 将被转换为 true
defineProps({
disabled: [Boolean, String],
})
// disabled 将被转换为 true
defineProps({
disabled: [Number, Boolean],
})
// disabled 将被解析为空字符串 (disabled="")
defineProps({
disabled: [String, Boolean],
})