Vue3 零基础每日笔记(042):props 与 emit 的 TS 写法 一、前言025 / 026 篇用运行时声明对象写法学过 props 和 emitTS 时代升级为纯类型声明definePropsT() 泛型 defineEmitsT() 泛型。升级后的体验父组件传错类型直接红线、传错字段名当场拦截——组件协议从注释约定变成编译器强制。二、defineProps 泛型interface 即协议对比两种声明方式// 旧·运行时声明JS 篇学过constpropsdefineProps({name:{type:String,required:true},age:{type:Number,default:0}})// 新·类型声明TS 版推荐interfaceProps{name:stringage?:number}constpropsdefinePropsProps()规则对照运行时写法类型写法含义type: String, required: truename: string必填type: Stringname?: string可选type: [String, Number]value: string | number多类型validator: v v 0泛型写不了另行校验复杂校验注意 validator 的取舍类型声明没法写校验函数——运行时校验需求如分数 0-100可以保留对象写法或干脆交给运行逻辑处理。两者也可以按 prop 混用吗不行——defineProps 要么全类型要么全对象二选一。完整子组件示例!-- src/components/UserCard.vue -- template div classcard h4{{ name }}/h4 p年龄{{ age }}/p ul li v-fortag in tags :keytag{{ tag }}/li /ul button clickemit(follow, name)关注 {{ name }}/button /div /template script setup langts interface Props { name: string age?: number tags?: string[] } const props definePropsProps() const emit defineEmits{ follow: [name: string] }() /script3.3 支持从外部文件导入类型接口复用不再受限// src/types/user.tsexportinterfaceUser{id:numbername:string}// 组件里importtype{User}from/types/userconstpropsdefineProps{user:User}()【截图位置父组件传错类型时编辑器红线的截图】三、withDefaults可选 prop 的默认值类型声明里没有 default 的位置默认值交给withDefaults宏interfaceProps{name:stringage?:numbertags?:string[]}// 第二参数给可选 prop 配默认值对象/数组仍是工厂函数constpropswithDefaults(definePropsProps(),{age:0,tags:()[]})三条记忆点必填 prop没?的不进默认值对象写了反而报错对象/数组默认值必须工厂函数025 篇铁律不变props.age的类型自动收窄为 number不再是 number | undefined——默认值参与类型运算Vue 3.5 新选择响应式 props 解构自带默认值可以替代 withDefaults// 3.5解构 默认值且保持响应式编译器魔法const{age0,tags[]}definePropsProps()老项目别模仿新项目按团队约定二选一两种都要认识。四、defineEmits 泛型载荷类型清单3.3 推荐元组语法事件名做 key、参数数组做 valueconstemitdefineEmits{follow:[name:string]// 载荷一个字符串change:[id:number,checked:boolean]// 载荷两个参数reset:[]// 无载荷空元组}()emit(follow,小明)// ✅emit(follow,123)// ❌ 红线要 stringemit(reset,多余的)// ❌ 红线reset 不收参数3.3 之前的老语法函数重载形式老项目大量存在要认识constemitdefineEmits{(e:follow,name:string):void(e:reset):void}()两种等价新代码用元组语法更清爽。父组件的收益监听处参数自动获得类型不用再猜载荷是什么。template UserCard name小明 :tags[前端, 学生] followonFollow / /template script setup langts import UserCard from ./components/UserCard.vue // name 自动推断为 string不用手标 function onFollow(name: string) { console.log(关注了, name) } /script传错属性同样被拦UserCard nmaex /→ 红线nmae 不存在。五、综合实战类型化的分页器组件把必填、可选、默认值、事件载荷全部串起来!-- src/components/MyPager.vue -- template div classpager button :disabledcurrent 1 clickgo(current - 1)上一页/button span第 {{ current }} / {{ Math.max(total, 1) }} 页/span button :disabledcurrent total clickgo(current 1)下一页/button /div /template script setup langts interface Props { current: number total: number size?: small | large // 字面量联合只能传这两个值 } const props withDefaults(definePropsProps(), { size: small }) const emit defineEmits{ change: [page: number] }() function go(page: number) { if (page 1 || page props.total || page props.current) return emit(change, page) } /script style scoped .pager { display: flex; gap: 12px; align-items: center; } /style!-- 父组件 -- template MyPager :currentpage :totaltotalPages sizelarge changepage $event / !-- ❌ sizebig → 红线只能是 small | large -- /template script setup langts import { ref, computed } from vue import MyPager from ./components/MyPager.vue const page ref(1) const pageSize 10 const totalCount ref(86) const totalPages computed(() Math.ceil(totalCount.value / pageSize)) /scriptsizebig这样的手滑字符串枚举当场拦截——真实项目里这类 bug 占比惊人。【截图位置分页器切换、size 传错值红线的运行效果】六、踩坑记录defineProps 泛型里引用了未导出的外部类型3.3 以下低版本只认本文件字面类型外部类型必须 3.3——版本不够就先把 interface 抄进组件withDefaults 给必填 prop 设默认值直接类型报错——默认值只属于可选 prop对象/数组默认值没写工厂老坑永不缺席tags: () []emits 元组写成对象change: { value: number }❌——是元组change: [value: number]方括号类型声明与运行时校验混搭defineProps 一次只能一种写法要 validator 就整体退回对象声明或在业务逻辑里校验父组件传 props 用 kebab 还是驼峰模板上:user-name✅TS 不影响这个规则但script setup里接收永远是驼峰props.userName七、今日小结definePropsinterface必填无?、可选?、多类型联合3.3 支持导入外部类型withDefaults配默认值对象/数组仍用工厂函数3.5 可用响应式解构默认值替代defineEmits 元组语法事件名: [参数类型列表]无载荷空元组收益父组件传参与监听全程类型提示协议即文档下篇预告3.4 的 defineModel 在 TS 下怎么写defineExpose 暴露的东西类型从哪来父组件拿子组件实例033 篇的 InstanceType 伏笔怎么标类型下一篇 043 defineModel 与 defineExpose 的 TS 用法。