在 Vue3 中,props(属性) 是父组件向子组件传递数据的单向通信机制,遵循“父→子”的数据流向,是组件复用和模块化的基础。子组件通过声明 props 接收外部数据,父组件通过属性绑定(v-bind 或简写 :)传递数据,确保组件状态的清晰和可控。
一、基础用法:从声明到使用
1. 子组件声明Props
Vue3 提供了两种声明 props 的方式,其中<script setup> 语法糖是最推荐的(更简洁、更符合 Composition API 风格)。
- 数组形式(简单场景):仅声明 prop 名称,适用于不需要复杂配置的场景。
// ChildComponent.vue(使用<script setup>) const props = defineProps(['title', 'count']) // 声明接收 title(String)和 count(Number)两个 props - 对象形式(推荐,复杂场景):可配置类型、默认值、必填性等,适用于需要严格验证的场景。
const props = defineProps({ title: { type: String, // 类型:字符串 required: true, // 必填 default: '默认标题' // 默认值(非必填时生效) }, count: { type: Number, // 类型:数字 default: 0 // 默认值 } })
2. 父组件传递Props
父组件通过属性绑定将数据传递给子组件,支持静态值和动态数据:
- 静态传递:直接写死值(适用于固定数据)。
<!-- ParentComponent.vue --> <template> <ChildComponent title="静态标题" :count="10" /> </template> - 动态传递:绑定父组件的
data或computed属性(适用于变化数据)。<template> <ChildComponent :title="parentTitle" :count="parentCount" /> </template> <script setup> import { ref } from 'vue' import ChildComponent from './ChildComponent.vue' const parentTitle = ref('动态标题') // 父组件 data const parentCount = ref(5) // 父组件 data </script>
3. 子组件使用Props
子组件通过模板或script访问 props:
- 模板中直接使用(最常见):
<template> <div> <h3>{{ title }}</h3> <p>数量:{{ count }}</p> </div> </template> - script 中通过
props访问(适用于逻辑处理):const props = defineProps(['title']) console.log('标题是:', props.title) // 输出父组件传递的 title
二、高级配置:类型验证与默认值
1. 类型验证
通过 type 属性指定 prop 的类型,Vue3 会在开发模式下检查类型是否匹配,不匹配则抛出警告。支持的类型包括:
- 原生类型:
String、Number、Boolean、Array、Object、Function、Symbol - 自定义类:通过
instanceof检查(如type: Person,需提前定义Person类)
const props = defineProps({
name: String, // 字符串
age: Number, // 数字
isActive: Boolean, // 布尔值
hobbies: Array, // 数组
profile: Object, // 对象
callback: Function, // 函数
id: Symbol // Symbol
})
2. 默认值与必填性
- 默认值:通过
default属性设置,当父组件未传递该 prop 时生效。- 基本类型(String、Number 等):直接赋值。
const props = defineProps({ count: { type: Number, default: 0 } // 默认值为 0 }) - 对象/数组:必须使用工厂函数返回(避免多个组件实例共享同一引用)。
const props = defineProps({ user: { type: Object, default: () => ({ name: '匿名用户', age: 18 }) }, tags: { type: Array, default: () => ['vue', 'javascript'] } })
- 基本类型(String、Number 等):直接赋值。
- 必填性:通过
required属性设置,未传递则抛出警告。const props = defineProps({ title: { type: String, required: true } // 必须传递 title })
3. 自定义验证函数
通过 validator 函数实现更复杂的验证逻辑(如值的范围、格式等)。函数接收 prop 值作为参数,返回 true(有效)或 false(无效),无效时会抛出警告。
const props = defineProps({
age: {
type: Number,
validator(value) {
return value >= 0 && value <= 150 // 年龄必须在 0-150 之间
}
},
status: {
validator(value) {
return ['active', 'inactive', 'pending'].includes(value) // 状态必须是这三个值之一
}
}
})
三、TypeScript 支持:类型安全的Props
Vue3 对 TypeScript 的支持极大提升了 props 的类型安全性。通过泛型和 PropType 工具,可以定义更精确的 props 类型。
1. 使用泛型定义Props
在 <script setup lang="ts"> 中,通过泛型参数指定 props 的类型:
<script setup lang="ts">
interface User {
name: string
age: number
}
const props = defineProps<{
user: User // 必填,类型为 User 接口
theme?: string // 可选,类型为 string
}>()
</script>
2. 使用 PropType 定义复杂类型
对于自定义类或更复杂的类型,使用 PropType 工具:
<script setup lang="ts">
import { PropType } from 'vue'
interface Book {
name: string
author: string
}
const props = defineProps({
book: {
type: Object as PropType<Book>, // 指定为 Book 类型
required: true
},
onUpdate: {
type: Function as PropType<(id: number) => void>, // 指定为函数类型
required: true
}
})
</script>
四、单向数据流:为什么不能直接修改Props?
Vue3 的单向数据流原则要求:父组件通过 props 向子组件传递数据,子组件不能直接修改 props。这一设计的目的:
- 避免数据混乱:如果多个子组件修改同一个 prop,会导致数据来源难以追踪。
- 提高可维护性:数据变更的路径清晰(父→子),便于调试和维护。
错误示例:直接修改 props
const props = defineProps(['count'])
const increment = () => {
props.count++ // ❌ 警告:Avoid mutating a prop directly
}
正确做法:通过事件通知父组件
子组件通过 $emit 触发自定义事件,父组件监听事件并修改数据。
<!-- 子组件 ChildComponent.vue -->
<template>
<button @click="increment">+1</button>
</template>
<script setup>
const props = defineProps(['count'])
const emit = defineEmits(['increment']) // 定义 increment 事件
const increment = () => {
emit('increment') // 触发 increment 事件
}
</script>
<!-- 父组件 ParentComponent.vue -->
<template>
<ChildComponent :count="parentCount" @increment="handleIncrement" />
</template>
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
const parentCount = ref(0)
const handleIncrement = () => {
parentCount.value++ // 父组件修改数据
}
</script>
五、实战案例:待办事项列表组件
场景描述
创建一个待办事项列表组件,接收待办事项数组(todos)和主题颜色(theme),支持添加和删除待办事项。
代码实现
1. 子组件:TodoList.vue
<template>
<div :class="['todo-list', theme]">
<h3>{{ title }}</h3>
<ul>
<li v-for="(todo, index) in todos" :key="index">
{{ todo.text }}
<button @click="deleteTodo(index)">删除</button>
</li>
</ul>
<input v-model="newTodo" placeholder="添加新待办" />
<button @click="addTodo">添加</button>
</div>
</template>
<script setup lang="ts">
import { ref, defineProps, defineEmits } from 'vue'
interface Todo {
text: string
completed: boolean
}
const props = defineProps<{
todos: Todo[] // 待办事项数组
title?: string // 标题(可选)
theme?: string // 主题(可选)
}>()
const emit = defineEmits<{
'update:todos': [todos: Todo[]] // 更新待办事项的事件
}>()
const newTodo = ref('')
const addTodo = () => {
if (newTodo.value.trim()) {
const updatedTodos = [...props.todos, { text: newTodo.value, completed: false }]
emit('update:todos', updatedTodos) // 触发更新事件
newTodo.value = ''
}
}
const deleteTodo = (index: number) => {
const updatedTodos = props.todos.filter((_, i) => i !== index)
emit('update:todos', updatedTodos) // 触发更新事件
}
</script>
<style scoped>
.todo-list {
padding: 20px;
border-radius: 8px;
margin: 10px;
}
.light { border: 2px solid #ccc; background: #fff; }
.dark { border: 2px solid #333; background: #f5f5f5; color: #333; }
</style>
2. 父组件:App.vue
<template>
<div>
<h1>待办事项列表</h1>
<TodoList
:todos="todos"
title="我的待办"
theme="light"
@update:todos="updateTodos"
/>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import TodoList from './components/TodoList.vue'
const todos = ref([
{ text: '学习 Vue3', completed: false },
{ text: '写一个待办应用', completed: false }
])
const updateTodos = (newTodos: any[]) => {
todos.value = newTodos // 更新父组件的 todos 数据
}
</script>
六、最佳实践
1. 命名规范
- props 定义:使用
camelCase(如userName),符合 JavaScript 变量命名习惯。 - 模板传递:使用
kebab-case(如user-name),因为 HTML 属性不区分大小写。<!-- 父组件模板 --> <ChildComponent :user-name="name" />
2. 单向数据流
始终通过事件通知父组件修改数据,避免直接修改 props。例如,子组件中需要修改 props 时,使用 emit 触发事件,父组件监听事件并更新数据。
3. 类型安全(TypeScript)
使用 TypeScript 定义 props 类型,提高代码的可维护性和健壮性。例如,通过泛型和 PropType 定义复杂类型,避免类型错误。
4. 性能优化
- 避免大型对象:不要通过 props 传递大型对象(如包含大量数据的数组),会增加组件渲染成本。
- 使用
v-once:如果 props 数据不会变化,使用v-once指令优化渲染性能。<ChildComponent :user="staticUser" v-once />
记住:props 是父子组件通信的基础,合理使用能让你的组件更清晰、更易维护。
18万+

被折叠的 条评论
为什么被折叠?



