Vue组件Props传参

Vue组件Props传参

_

🎯 什么是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核心原则:

  1. 单向数据流:数据从父流向子,子组件不能直接修改props

  2. 类型安全:使用对象形式定义props,提供类型检查

  3. 默认值:为非必填props提供合理的默认值

  4. 验证机制:使用validator函数进行自定义验证

最佳实践:

  1. 命名规范:使用kebab-case传递props,camelCase接收

  2. 类型定义:始终使用对象形式定义props

  3. 数据同步:通过emit事件通知父组件修改数据

  4. 本地副本:需要编辑时创建本地副本

  5. 避免直接修改:永远不要直接修改props

  6. 避免引用复制:不要简单地将props赋值给ref

常见场景解决方案:

  • 只读展示:直接使用props

  • 需要编辑:创建本地副本 + emit事件

  • 双向绑定:使用v-model

  • 复杂对象:使用computed或watch

正确使用props能够构建可维护、可预测的组件架构,让Vue应用的数据流更加清晰!

如何监听一个变量值的变化 2024-06-20
CSS界面浮现优先级 2024-06-03

评论区