【Vue3】defineProps:子组件接收父组件传递的数据

📅 发布时间:2026/7/15 16:48:30 👁️ 浏览次数:
【Vue3】defineProps:子组件接收父组件传递的数据
1.1 核心作用与底层原理defineProps 用于声明组件从父组件接收的属性Props替代 Vue2 选项式 API 中的 props 选项其底层原理是编译阶段defineProps 会被转换为组件实例的 props 选项同时自动为 Props 添加响应式特性无需手动用 ref 包裹结合 TypeScript 时会在编译期完成类型校验杜绝父组件传参类型错误、漏传必选参数等问题减少运行时异常。核心优势类型安全、写法简洁、响应式自动绑定、支持复杂校验。【青柠代码录】— 青柠来相伴代码更简单【全栈】博客合集https://www.yuque.com/u12587869/zplytb/ur5ohwqxd2axtiny 【Java】面试题https://www.yuque.com/u12587869/zplytb/eh7yqzitiab693og 1.2 用法体系1.2.1 基础写法TS 泛型标注最推荐Vue3.3 开始支持直接通过 TypeScript 泛型标注 Props 类型写法最简洁类型推导最完整是企业开发的首选方式。template div classprops-basic h3{{ title }}/h3 p v-ifdesc描述{{ desc }}/p p数量{{ count }}/p button :disableddisabled点击按钮/button /div /template ​ script setup langts // 直接用 TS 泛型定义 Props 类型 // 格式defineProps{ 字段名: 类型; 可选字段?: 类型 }() const props defineProps{ // 必传字段无默认值父组件必须传递 title: string; // 可选字段父组件可传可不传默认值为 undefined desc?: string; // 数字类型必传 count: number; // 布尔类型可选 disabled?: boolean; // 数组类型必传指定数组元素类型 tags: string[]; }(); ​ // 脚本中访问 Props响应式可直接使用无需 .value console.log(props.title); // 类型提示string console.log(props.desc); // 类型提示string | undefined // 注意Props 是只读的不能直接修改单向数据流原则 // props.count 10; // 报错Cannot assign to count because it is a read-only property /script ​ style scoped .props-basic { padding: 16px; border: 1px solid #e5e7eb; border-radius: 8px; } /style1.2.2 高级写法带默认值Vue3.3 withDefaults纯 TS 泛型标注无法设置默认值Vue3.3 提供了 withDefaults 辅助函数可同时实现「类型标注」和「默认值设置」兼顾简洁性和实用性。推荐将 Props 类型抽离为接口interface提升代码可维护性尤其适合复杂 Props。template div classprops-with-default h3{{ title }}/h3 p描述{{ desc }}/p p数量{{ count }}/p p标签{{ tags.join(, ) }}/p /div /template ​ script setup langts // 1. 抽离 Props 类型为接口推荐便于复用和维护 interface Props { // 必传字段无默认值 title: string; // 可选字段设置默认值 desc?: string; count?: number; tags?: string[]; // 嵌套对象类型可选带默认值 user?: { name: string; age?: number; }; } ​ // 2. 使用 withDefaults 设置默认值 // 格式withDefaults(defineProps接口名(), { 可选字段: 默认值 }) const props withDefaults(definePropsProps(), { // 字符串默认值 desc: 暂无描述, // 数字默认值 count: 0, // 数组默认值必须用函数返回避免引用类型共享 tags: () [默认标签1, 默认标签2], // 嵌套对象默认值同样用函数返回 user: () ({ name: 匿名用户, age: 18 }) }); ​ // 访问默认值父组件未传递时自动使用默认值 console.log(props.desc); // 父组件未传时输出暂无描述 console.log(props.user?.name); // 父组件未传时输出匿名用户 /script关键注意数组、对象等引用类型的默认值必须用「函数返回」否则多个组件实例会共享同一个引用导致数据污染比如多个组件修改 tags 数组会互相影响。1.2.3 兼容写法运行时校验复杂场景适用如果需要更复杂的校验逻辑如自定义校验器、必填提示可以使用「运行时声明」写法兼容 Vue2 的 props 配置方式同时支持 TS 类型推导。适合场景价格校验、手机号校验、枚举值校验等。template div classprops-runtime p商品价格¥{{ price }}/p p商品等级{{ level }}/p /div /template ​ script setup langts // 运行时声明写法对象形式支持 type、required、validator、default const props defineProps({ // 1. 基础校验必传、数字类型 price: { type: Number, // 类型支持 String/Number/Boolean/Array/Object/Function 等 required: true, // 必传父组件未传时控制台会报警告 description: 商品价格非负数, // 组件文档说明便于团队协作 // 自定义校验器返回 true 表示校验通过false 表示失败失败会报警告 validator: (value: number) { if (value 0) { console.error(商品价格不能为负数); return false; } return true; } }, // 2. 枚举值校验只能传递指定的值 level: { type: String, required: true, // 枚举值只能是 primary/secondary/tertiary validator: (value: string) { return [primary, secondary, tertiary].includes(value); }, default: primary // 非必传时设置默认值 }, // 3. 多类型支持允许传递 String 或 Number 类型 id: { type: [String, Number], // 支持多种类型 required: true } }); /script补充运行时校验和 TS 类型标注可以结合使用吗不建议。两种写法选一种即可混合使用会导致类型冗余且可能出现校验逻辑与类型标注不一致的问题。1.3 应用场景商品卡片组件 Props 封装真实电商场景中商品卡片组件需要接收多种数据且部分字段有校验、默认值要求!-- src/components/ProductCard.vue -- template div classproduct-card img :srcimage alt商品图片 classproduct-img h3 classproduct-title{{ title }}/h3 p classproduct-price¥{{ price.toFixed(2) }}/p p v-iforiginalPrice classproduct-original-price 原价del¥{{ originalPrice.toFixed(2) }}/del /p p classproduct-stock库存{{ stock }}/p /div /template ​ script setup langts // 抽离商品 Props 类型可复用比如在父组件传递数据时也能使用 export interface ProductCardProps { // 商品ID必传支持字符串/数字 id: string | number; // 商品标题必传 title: string; // 商品现价必传非负数 price: number; // 商品原价可选 originalPrice?: number; // 商品图片必传 image: string; // 商品库存可选默认0 stock?: number; // 商品标签可选默认空数组 tags?: string[]; } ​ // 声明 Props 并设置默认值 const props withDefaults(definePropsProductCardProps(), { stock: 0, tags: () [] }); ​ // 衍生计算属性基于 Props 推导无需额外声明 const isDiscount computed(() { return props.originalPrice props.originalPrice props.price; }); /script ​ style scoped .product-card { width: 320px; border: 1px solid #e5e7eb; border-radius: 8px; padding: 16px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); } .product-img { width: 100%; height: 200px; object-fit: cover; border-radius: 4px; margin-bottom: 12px; } .product-title { font-size: 16px; margin-bottom: 8px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .product-price { color: #f53f3f; font-size: 18px; margin-bottom: 4px; } .product-original-price { color: #969799; font-size: 14px; margin-bottom: 8px; } .product-stock { color: #666; font-size: 14px; } /style父组件使用示例类型提示完整避免传参错误!-- src/views/ProductList.vue -- template div classproduct-list h2商品列表/h2 div classcard-container ProductCard v-forproduct in productList :keyproduct.id :idproduct.id :titleproduct.title :priceproduct.price :original-priceproduct.originalPrice :imageproduct.image :stockproduct.stock :tagsproduct.tags / /div /div /template ​ script setup langts import ProductCard, { type ProductCardProps } from /components/ProductCard.vue; import { ref } from vue; ​ // 父组件传递的数据类型与子组件 Props 一致复用子组件的接口 const productList refProductCardProps[]([ { id: 1, title: Vue3 TS 企业级实战课程, price: 199, originalPrice: 299, image: /images/course1.jpg, stock: 50, tags: [热门, 实战] }, { id: 2, title: TypeScript 进阶指南, price: 89, image: /images/course2.jpg, stock: 30, tags: [进阶, TypeScript] } ]); /script1.4 注意事项Props 是只读的子组件不能直接修改 Props单向数据流原则如果需要修改需通过 defineEmits 触发父组件修改否则会报错且导致数据不一致。引用类型默认值必须用函数返回数组、对象等引用类型默认值不能直接写字面量如 tags: []必须用函数返回tags: () []避免多个组件实例共享引用。避免传递过大的对象不要把整个接口返回的原始数据作为 Props 传递建议拆解为细粒度的字段如只传 id、title、price而非整个 product 对象提升组件复用性和维护性。必传字段一定要标注 required无论是 TS 泛型标注必传字段不加 ?还是运行时声明required: true都要明确必传字段避免父组件漏传导致的运行时错误。TS 类型与运行时校验一致如果使用运行时校验确保 validator 逻辑与 TS 类型标注一致如 TS 标注 price 是 numbervalidator 就不能允许非数字值。