Vue 3 核心进阶:掌握 Props 传参及 TypeScript 最佳实践

在 Vue 3 的组件化开发中,props 是实现父子组件解耦、数据流动的核心机制。本文将带你从基础语法到 TypeScript 高级校验,全面掌握 props 的使用。


一、 Props 的基本定义

<script setup> 语法糖中,我们使用 defineProps 宏来声明组件接收的属性。

1. 数组模式(不推荐)

仅用于快速原型开发,缺乏类型约束。

const props = defineProps(['name', 'age'])

2. 对象模式

可以指定基础类型、默认值和是否必传。

const props = defineProps({
  name: String,
  age: {
    type: Number,
    default: 18,
    required: true
  }
})

二、 结合 TypeScript 的高级用法

在使用 lang="ts" 时,使用 类型标注 是官方最推荐的做法,它能提供强大的 IDE 自动补全和编译时检查。

1. 纯类型声明

这种方式最简洁,直接定义接口即可。

interface User {
  id: string
  name: string
}

const props = defineProps<{
  title: string
  list: User[]
  status?: 'active' | 'inactive' // 使用联合类型约束取值范围
}>()

这里的status表示是可选的。并且值只能是active | inactive

2. 默认值处理:withDefaults

由于 TS 类型声明模式下无法直接写 default,Vue 提供了 withDefaults 宏:

const props = withDefaults(defineProps<{
  title?: string
  count?: number
}>(), {
  title: '默认标题',
  count: 0
})

三、 核心原则:单向数据流

这是 Props 使用中最重要的准则。

  • 数据是只读的:子组件绝对不能直接修改 props 的成员(例如 props.title = 'xxx')。
  • 为什么? 这样可以防止子组件意外修改父组件的状态,导致数据流向难以追踪。

如果你需要修改这个值,有两种规范做法:

  1. 本地化: 将其作为 ref 的初始值(仅限初始同步)。
  2. 派生化: 使用 computed 进行转换。

在大多数场景下,子组件应该抛出一个事件来通知父组件做出改变。


四、 综合代码案例:Person 组件

通过父组件App.vue向子组件Person.vue传入Person数组进行展示。

1. 定义类型 (src/types/index.ts)

export interface Person {
  id: string
  name: string
  age: number
}
export type Persons = Person[]

2. 子组件 (Person.vue)

<script setup lang="ts">
import { type Persons } from '@/types'

// 1. 声明 Props
const props = withDefaults(defineProps<{
  list: Persons
  listTitle?: string
}>(), {
  listTitle: '成员列表'
})
</script>

<template>
  <div class="person-card">
    <h4>{{ listTitle }}</h4>
    <hr />
    <ul>
      <li v-for="p in list" :key="p.id">
        {{ p.name }} - {{ p.age }} 岁
      </li>
    </ul>
  </div>
</template>

<style scoped>
.person-card {
  border: 1px solid #42b883;
  padding: 15px;
  border-radius: 8px;
  background: #f9f9f9;
}
</style>

3. 父组件 (App.vue)

<script setup lang="ts">
import { ref } from 'vue'
import Person from './components/Person.vue'
import { type Persons } from './types'

// 父组件准备响应式数据
const users = ref<Persons>([
  { id: '01', name: '张三', age: 28 },
  { id: '02', name: '李四', age: 32 }
])
</script>

<template>
  <div class="app-container">
    <h1>Vue 3 Props 演示</h1>
    <Person :list="users" listTitle="技术部员工指南" />
  </div>
</template>

五、 避坑指南与技巧

1. 解构 Props 会失去响应式

如果你写 const { list } = defineProps(...),那么当父组件更新 list 时,子组件解构出来的变量不会更新。

  • 解决方法:使用 toRefs
import { toRefs } from 'vue'
const props = defineProps<{ count: number }>()
const { count } = toRefs(props) // 此时 count 是响应式的

2. 深度监听与引用类型

当 Props 传递的是对象或数组时,子组件内虽然不能替换整个对象,但由于 JS 引用类型的特性,修改对象内部属性(如 props.list[0].name = 'xxx')虽然能生效,但强烈不建议这样做,因为它违反了单向数据流。


总结:
props 是组件的“输入接口”,通过 TypeScript 加持后的 defineProps 能够让你的组件库具备极高的健壮性。

posted @ 2026-01-04 03:46  雨中遐想  阅读(19)  评论(0)    收藏  举报