Props 详解
本教程共 40 篇 · 第 15 篇 · 更新于 2026-07-29 · 约 8 分钟阅读
15. Props 详解
本节目标:掌握 Props 的完整用法,包括声明、验证、单向数据流和命名规范,让你的组件接口清晰可靠。
Props 是什么
Props 是父组件传递给子组件的自定义属性。你可以把它理解为函数的参数——父组件传入什么,子组件就接收什么。
Props 的声明方式
在 <script setup> 中,使用 defineProps() 宏来声明:
<script setup>
// 数组语法——简单但不验证类型
defineProps(['title', 'likes'])
</script>
数组写法很方便,但没法告诉别人这个 prop 应该是什么类型。实际项目中更推荐对象语法:
<script setup>
// 对象语法——可以指定类型
defineProps({
title: String,
likes: Number
})
</script>
对象语法不只是文档——如果你传错了类型,Vue 会在浏览器控制台发出警告。
运行时验证
对象语法的真正威力在于验证。你可以为每个 prop 指定详细的规则:
defineProps({
// 基础类型检查
propA: Number,
// 允许多种类型
propB: [String, Number],
// 必填字符串
propC: {
type: String,
required: true
},
// 带默认值的数字
propE: {
type: Number,
default: 100
},
// 对象类型的默认值——必须用工厂函数
propF: {
type: Object,
default(rawProps) {
return { message: 'hello' }
}
},
// 自定义验证函数
propG: {
validator(value, props) {
return ['success', 'warning', 'danger'].includes(value)
}
},
// 函数类型的默认值
propH: {
type: Function,
// 对象和数组不同,这里是直接返回默认函数
default() {
return 'Default function'
}
}
})
Note对象和数组的默认值必须用工厂函数返回,不能直接写对象字面量。这是为了避免多个组件实例共享同一个引用。
验证规则一览
| 验证选项 | 说明 |
|---|---|
type | 期望的类型(原生构造函数或自定义类) |
required | 是否为必填 |
default | 默认值(对象/数组必须用工厂函数) |
validator | 自定义验证函数,返回 true/false |
type 可以是以下原生构造函数:String、Number、Boolean、Array、Object、Date、Function、Symbol、Error。
也可以是你自己的类,Vue 会用 instanceof 来检查。
可空类型
如果 prop 必填但允许 null,用数组语法包含 null:
defineProps({
id: {
type: [String, null],
required: true
}
})
Warning如果
type只写null而不用数组语法,那将允许任何类型通过。
命名约定
JavaScript 中用 camelCase,模板中用 kebab-case:
// 组件内声明用 camelCase
defineProps({
greetingMessage: String
})
<!-- 父组件传递时用 kebab-case -->
<MyComponent greeting-message="hello" />
为什么这样设计?camelCase 在 JavaScript 里是合法的标识符,可以直接在模板表达式里用。而 kebab-case 和 HTML 属性的命名习惯一致。
静态 vs 动态 Props
静态 prop 直接写值:
<BlogPost title="My journey with Vue" />
动态 prop 用 v-bind(缩写 :):
<!-- 绑定变量 -->
<BlogPost :title="post.title" />
<!-- 绑定表达式 -->
<BlogPost :title="post.title + ' by ' + post.author.name" />
传递不同类型的值
数字——不加 v-bind 会被当字符串:
<!-- 错误:传的是字符串 "42" -->
<BlogPost likes="42" />
<!-- 正确:传的是数字 42 -->
<BlogPost :likes="42" />
布尔值——只写属性名表示 true:
<!-- 等价于 :is-published="true" -->
<BlogPost is-published />
<!-- 传 false 必须用 v-bind -->
<BlogPost :is-published="false" />
数组和对象——必须用 v-bind:
<BlogPost :comment-ids="[234, 266, 273]" />
<BlogPost :author="{ name: 'Veronica', company: 'Veridian Dynamics' }" />
用一个对象批量传 prop
v-bind 不跟属性名,会把对象的所有属性都传进去:
const post = {
id: 1,
title: 'My Journey with Vue'
}
<!-- 等价于 :id="post.id" :title="post.title" -->
<BlogPost v-bind="post" />
单向数据流
这是 Vue 组件通信的第一原则:prop 只能从父组件流向子组件,反过来不行。
每次父组件更新,子组件的 prop 都会刷新为最新值。如果你尝试在子组件里修改 prop,Vue 会警告你:
const props = defineProps(['foo'])
// ❌ 警告!prop 是只读的
props.foo = 'bar'
想修改 prop 怎么办
有两种常见场景。
场景一:prop 只是初始值——用 ref 断开连接:
const props = defineProps(['initialCounter'])
// counter 只用 props.initialCounter 作为初始值
// 后续不再跟随 prop 更新
const counter = ref(props.initialCounter)
场景二:需要对 prop 做转换——用计算属性:
const props = defineProps(['size'])
// 自动跟随 prop 更新
const normalizedSize = computed(() => props.size.trim().toLowerCase())
对象和数组的特殊情况
JavaScript 中对象和数组是引用传递。子组件虽然不能替换整个 prop,但可以修改其内部的属性:
// 子组件内部
props.foo.bar = 'baz' // 技术上可行
这样做会让数据流变得不清晰。最佳实践是:要么用 readonly() 保护数据,要么让子组件 emit 事件通知父组件来修改。
Boolean 强制转换
声明为 Boolean 类型的 prop 有特殊转换规则:
defineProps({
disabled: Boolean
})
<!-- 等价于 :disabled="true" -->
<MyComponent disabled />
<!-- 等价于 :disabled="false" -->
<MyComponent />
当 prop 允许多个类型时,Boolean 的转换规则取决于顺序——Boolean 在 String 前面才会生效:
// disabled 会被强制转换为 true
defineProps({ disabled: [Boolean, Number] })
defineProps({ disabled: [Boolean, String] })
// 不会触发强制转换,disabled 会按 Number 类型解析
defineProps({ disabled: [Number, Boolean] })
// disabled 会被解析为空字符串 ""
defineProps({ disabled: [String, Boolean] })
TipBoolean 强制转换在 String 之前才生效。
[Boolean, String]转换,[String, Boolean]不转换。[Number, Boolean]也不会触发强制转换。
本节回顾
- Props 用
defineProps()声明,推荐对象语法做类型验证 - 验证选项包括
type、required、default、validator - prop 命名:JavaScript 用 camelCase,模板用 kebab-case
- 单向数据流——prop 只读,修改用本地 ref 或 computed
- Boolean 类型的 prop 有特殊强制转换规则,注意和 String 的顺序