🎯 什么是Props?
Props是Vue中父组件向子组件传递数据的主要方式。Props是单向数据流,数据从父组件流向子组件,子组件不能直接修改props,这保证了数据的可预测性和组件的独立性。
📤 父组件:传递Props
基本语法
<!-- ParentComponent.vue -->
<template>
<div class="parent">
<!-- 静态传递:直接传递字符串 -->
<Student name="李四" sex="女" />
<!-- 动态传递:使用v-bind(简写为:)传递响应式数据 -->
<Student
:name="studentName"
:age="studentAge"
:is-active="isActive"
/>
<!-- 传递对象 -->
<RoomInfo :room="currentRoom" />
<!-- 传递数组 -->
<ScoreList :scores="studentScores" />
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import Student from './Student.vue'
import RoomInfo from './RoomInfo.vue'
import ScoreList from './ScoreList.vue'
// 响应式数据
const studentName = ref('张三')
const studentAge = ref(20)
const isActive = ref(true)
// 对象数据
const currentRoom = reactive({
id: 1,
computer_room_name: '计算机实验室A',
capacity: 30,
equipment: ['电脑', '投影仪']
})
// 数组数据
const studentScores = ref([
{ subject: '数学', score: 95 },
{ subject: '英语', score: 88 }
])
</script>Props传递方式对比
<template>
<!-- 1. 静态Props - 传递固定值 -->
<UserCard name="默认用户" age="25" />
<!-- 2. 动态Props - 传递变量 -->
<UserCard :name="userName" :age="userAge" />
<!-- 3. 传递布尔值 -->
<UserButton :disabled="isDisabled" />
<!-- 4. 传递数字 -->
<ProgressBar :max="100" :current="progress" />
<!-- 5. 传递整个对象 -->
<UserDetail :user="currentUser" />
<!-- 6. 使用展开运算符传递多个props -->
<UserCard v-bind="userProps" />
</template>
<script setup>
const isDisabled = ref(false)
const progress = ref(75)
const currentUser = reactive({ name: '李四', age: 22, email: 'lisi@example.com' })
// 使用展开运算符
const userProps = reactive({
name: '王五',
age: 28,
isVip: true
})
</script>📥 子组件:接收Props
基本接收方式
<!-- Student.vue -->
<template>
<div class="student-card">
<h3>学生信息</h3>
<p>姓名:{{ name }}</p>
<p>性别:{{ sex }}</p>
<p>年龄:{{ age }}</p>
</div>
</template>
<script setup>
// 方式1:数组形式(简单但不推荐)
// defineProps(['name', 'sex', 'age'])
// 方式2:对象形式(推荐,支持类型检查和默认值)
const props = defineProps({
name: {
type: String,
required: true, // 必填属性
validator: (value) => value.length > 0 // 自定义验证
},
sex: {
type: String,
default: '未知',
validator: (value) => ['男', '女', '未知'].includes(value)
},
age: {
type: Number,
default: 0,
validator: (value) => value >= 0 && value <= 150
}
})
// 直接使用props
console.log('学生姓名:', props.name)
console.log('学生年龄:', props.age)
</script>TypeScript支持
<!-- UserCard.vue -->
<template>
<div class="user-card">
<h3>{{ user.name }}</h3>
<p>年龄:{{ user.age }}</p>
<p>邮箱:{{ user.email }}</p>
<button :class="{ active: user.isActive }">
{{ user.isActive ? '活跃' : '非活跃' }}
</button>
</div>
</template>
<script setup lang="ts">
// TypeScript接口定义
interface User {
id: number
name: string
age: number
email: string
isActive: boolean
}
// 使用TypeScript的props定义
interface Props {
user: User
onUpdate?: (user: User) => void
readonly?: boolean
}
const props = withDefaults(defineProps<Props>(), {
readonly: false,
onUpdate: () => {}
})
</script>⚠️ 常见问题和最佳实践
❌ 错误做法:直接修改Props
<!-- 错误示例 -->
<template>
<div>
<p>{{ room.computer_room_name }}</p>
<!-- ❌ 错误:props是只读的,不应该直接修改 -->
<button @click="modifyRoom">修改房间</button>
</div>
</template>
<script setup>
// ❌ 错误:直接修改props
const props = defineProps({
room: {
type: Object,
required: true
}
})
// ❌ 错误:将props转换为ref也不推荐,因为会造成数据不一致
// const room111 = ref(props.room)
const modifyRoom = () => {
// ❌ 这样写会有警告,且不推荐
props.room.computer_room_name = '新名称'
}
</script>✅ 正确做法:通过事件通知父组件
<!-- RoomInfo.vue -->
<template>
<div class="room-info">
<h3>{{ room.computer_room_name }}</h3>
<p>容量:{{ room.capacity }}</p>
<!-- 使用本地副本进行编辑 -->
<div v-if="isEditing">
<input v-model="localRoom.computer_room_name" placeholder="房间名称" />
<button @click="saveChanges">保存</button>
<button @click="cancelEdit">取消</button>
</div>
<button v-else @click="startEdit">编辑</button>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
const props = defineProps({
room: {
type: Object,
required: true
}
})
// 定义emit事件
const emit = defineEmits(['update:room', 'room-changed'])
// 编辑状态
const isEditing = ref(false)
// 创建本地副本进行编辑
const localRoom = ref({ ...props.room })
// 监听props变化,同步到本地副本
watch(() => props.room, (newRoom) => {
localRoom.value = { ...newRoom }
}, { deep: true })
// 开始编辑
const startEdit = () => {
isEditing.value = true
localRoom.value = { ...props.room }
}
// 保存更改
const saveChanges = () => {
// 通过emit通知父组件
emit('update:room', localRoom.value)
emit('room-changed', localRoom.value)
isEditing.value = false
}
// 取消编辑
const cancelEdit = () => {
localRoom.value = { ...props.room }
isEditing.value = false
}
</script>🔄 父子组件数据同步
使用v-model实现双向绑定
<!-- 父组件 ParentComponent.vue -->
<template>
<div>
<!-- v-model自动处理props的传递和update事件 -->
<RoomEditor v-model="currentRoom" />
<!-- 等价于下面的写法 -->
<!-- <RoomEditor :room="currentRoom" @update:room="currentRoom = $event" /> -->
<p>当前房间:{{ currentRoom.computer_room_name }}</p>
</div>
</template>
<script setup>
import { reactive } from 'vue'
import RoomEditor from './RoomEditor.vue'
const currentRoom = reactive({
id: 1,
computer_room_name: '计算机实验室A',
capacity: 30
})
</script><!-- 子组件 RoomEditor.vue -->
<template>
<div class="room-editor">
<h3>编辑房间信息</h3>
<input
v-model="localRoom.computer_room_name"
placeholder="房间名称"
@input="updateRoom"
/>
<input
v-model.number="localRoom.capacity"
type="number"
placeholder="容量"
@input="updateRoom"
/>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
const props = defineProps({
// v-model默认使用modelValue作为prop名
modelValue: {
type: Object,
required: true
}
})
const emit = defineEmits(['update:modelValue'])
const localRoom = ref({ ...props.modelValue })
// 监听本地变化,emit更新
const updateRoom = () => {
emit('update:modelValue', localRoom.value)
}
// 监听props变化
watch(() => props.modelValue, (newValue) => {
localRoom.value = { ...newValue }
}, { deep: true })
</script>自定义v-model
<!-- 父组件 -->
<template>
<RoomInfo
v-model:room="roomData"
v-model:loading="isLoading"
/>
</template>
<!-- 子组件 -->
<script setup>
const props = defineProps({
room: Object,
loading: Boolean
})
const emit = defineEmits(['update:room', 'update:loading'])
// 更新room
const updateRoom = (newRoom) => {
emit('update:room', newRoom)
}
// 更新loading
const updateLoading = (newLoading) => {
emit('update:loading', newLoading)
}
</script>💡 高级用法
Props验证
const props = defineProps({
// 基础类型检查
name: String,
age: Number,
isActive: Boolean,
// 多个可能类型
value: [String, Number],
// 必填字段
requiredProp: {
type: String,
required: true
},
// 默认值
optionalProp: {
type: Number,
default: 0
},
// 对象/数组的默认值应使用工厂函数
complexProp: {
type: Object,
default: () => ({
key: 'default'
})
},
// 自定义验证函数
customProp: {
type: String,
validator: (value) => {
// 返回true表示验证通过
return ['success', 'warning', 'error'].includes(value)
}
}
})动态Props
<template>
<!-- 动态属性名 -->
<component
v-bind="dynamicProps"
:[dynamicKey]="dynamicValue"
/>
</template>
<script setup>
import { ref, computed } from 'vue'
const dynamicKey = ref('title')
const dynamicValue = ref('动态标题')
// 动态props对象
const dynamicProps = computed(() => ({
name: '组件名称',
age: 25,
[dynamicKey.value]: dynamicValue.value
}))
</script>📝 总结
Props核心原则:
单向数据流:数据从父流向子,子组件不能直接修改props
类型安全:使用对象形式定义props,提供类型检查
默认值:为非必填props提供合理的默认值
验证机制:使用validator函数进行自定义验证
最佳实践:
✅ 命名规范:使用kebab-case传递props,camelCase接收
✅ 类型定义:始终使用对象形式定义props
✅ 数据同步:通过emit事件通知父组件修改数据
✅ 本地副本:需要编辑时创建本地副本
❌ 避免直接修改:永远不要直接修改props
❌ 避免引用复制:不要简单地将props赋值给ref
常见场景解决方案:
只读展示:直接使用props
需要编辑:创建本地副本 + emit事件
双向绑定:使用v-model
复杂对象:使用computed或watch
正确使用props能够构建可维护、可预测的组件架构,让Vue应用的数据流更加清晰!