
【TypeScript Vue】前端开发从 any 滥用到类型安全统一接口/Props/表单类型写法避开类型混乱与运行时报错坑 文章目录一、前言为什么要写这篇二、先搞清楚类型到底是什么2.1 类型不是「玄学」是「约束」2.2 any 为什么是「万恶之源」三、核心规范一接口Interface怎么设计3.1 用 Interface 定义「数据结构」3.2 可选、必选、只读3.3 常见踩坑后端字段和前端不一致四、核心规范二Vue Props 类型怎么写4.1 用 Interface 定义 Props4.2 有默认值的 Props4.3 Props 常见坑五、核心规范三表单类型统一5.1 表单值和接口数据分开5.2 完整示例新增/编辑表单5.3 表单校验类型如 Element Plus六、核心规范四从 any 过渡到类型安全6.1 替代 any 的几种写法6.2 unknown 的正确用法6.3 泛型让类型跟着数据走七、编码规范速查7.1 命名7.2 组织方式7.3 可以记一下的规则八、小结 系列模块导航同学们好我是 Eugene尤金一名多年中后台前端开发工程师。Eugene 发音 /juːˈdʒiːn/大家怎么顺口怎么叫就好很多前端开发者都会遇到一个瓶颈代码能跑但不够规范功能能实现但维护起来特别痛苦一个人写没问题一到团队协作就各种混乱、踩坑、返工。想写出干净、优雅、可维护的专业代码靠的不是天赋而是体系化的规范 真实实战经验。这一系列《前端规范实战》我会用大白话 真实业务场景不讲玄学、不堆理论只分享能直接落地的规范、标准与避坑指南。帮你从「会写代码」真正升级为「会写优质、可维护、团队级别的代码」。TS 类型规范从 any 到类型安全接口/Props/表单类型统一编码语法规范篇一、前言为什么要写这篇日常写 Vue 时很多人都在用 TS但类型往往是「能用就行」要么到处any要么类型和实际数据对不上。这篇不讲太底层的原理重点放在日常该怎么写、为什么这么写、容易踩的坑在哪适合会写 JS但对 TS 类型有点懵的同学从零学 TS 的初学者想巩固类型基础、顺便改掉坏习惯的熟手结构上会按基础概念 → 实战规范 → 常见坑来写尽量用完整示例 文字说明讲清楚。⬆ 返回目录二、先搞清楚类型到底是什么2.1 类型不是「玄学」是「约束」类型可以理解成给变量、函数、对象加一层「说明书」告诉你这个变量能放什么函数要传什么、会返回什么对象里有哪些字段、分别是什么类型有了类型TS 就能在写代码时帮你发现错误而不是等到运行时才报错。⬆ 返回目录2.2 any 为什么是「万恶之源」// ❌ any 写法编译器完全不管了letuser:any{name:张三};user.xxx.yyy.zzz;// 编译通过但运行时报错user();// 编译通过但 user 根本不是函数一旦用了anyTS 就放弃检查等于把 TS 的优势全部丢掉。规范第一条尽量不用 any如果暂时不知道类型用unknown再断言或收窄。⬆ 返回目录三、核心规范一接口Interface怎么设计3.1 用 Interface 定义「数据结构」接口用来描述对象的结构最常用的场景就是接口数据、列表项、配置对象。// ✅ 推荐用 interface 描述接口返回的数据interfaceUserInfo{id:number;name:string;avatar?:string;// 可选字段用 ?createdAt:string;}// 使用constuser:UserInfo{id:1,name:张三,createdAt:2025-03-20// avatar 可以不写因为是可选};⬆ 返回目录3.2 可选、必选、只读interfaceFormConfig{required:boolean;// 必填maxLength?:number;// 可选readonlyid:string;// 只读创建后不能再改}⬆ 返回目录3.3 常见踩坑后端字段和前端不一致后端经常是user_name前端习惯userName可以这样约定// 方案一接口类型按后端字段来转一层再用interfaceUserApiResponse{user_id:number;user_name:string;created_at:string;}// 转换函数 前端使用的类型interfaceUser{userId:number;userName:string;createdAt:string;}functiontransformUser(apiUser:UserApiResponse):User{return{userId:apiUser.user_id,userName:apiUser.user_name,createdAt:apiUser.created_at};}// 方案二保持和后端一致前端直接用interfaceUser{user_id:number;user_name:string;created_at:string;}建议接口类型和接口字段一一对应避免userId/user_id混用导致类型和实际不符。⬆ 返回目录四、核心规范二Vue Props 类型怎么写4.1 用 Interface 定义 Propsscriptsetuplangts// 1. 先定义 Props 的接口interfaceProps{title:string;count?:number;// 可选有默认值list:string[];user?:UserInfo;// 复杂对象用已有接口}// 2. 用 defineProps 并指定泛型constpropsdefinePropsProps();// 3. 使用时都有类型提示console.log(props.title);// stringconsole.log(props.count);// number | undefined/scripttemplatediv{{ props.title }}/div/template⬆ 返回目录4.2 有默认值的 Props// 方式一withDefaults 泛型interfaceProps{title:string;count?:number;}constpropswithDefaults(definePropsProps(),{count:0// 默认值});// 方式二分开写Vue 3.3 推荐interfaceProps{title:string;count?:number;}constpropswithDefaults(definePropsProps(),{count:()0// 对象/数组用工厂函数});⬆ 返回目录4.3 Props 常见坑坑 1必传的没传类型却没报错// ❌ 子组件要求必传 user但父组件没传可能只在运行时才发现 interface Props { user: UserInfo; // 必传 } // ✅ 父组件使用时要确保传入ChildComponent:usercurrentUser/坑 2直接改 props// ❌ 不要直接改 propsprops.count10;// 报错props 是只读的// ✅ 需要修改时用本地状态constlocalCountref(props.count??0);⬆ 返回目录五、核心规范三表单类型统一5.1 表单值和接口数据分开表单里的值如空字符串、未选状态和接口期望的类型往往不一样要单独定义表单类型。// 接口/列表用的类型interfaceUser{id:number;name:string;age:number;department:string;}// 表单类型所有字段可选空值用 或 undefinedinterfaceUserForm{name:string;age:number|;// 输入框可能是空字符串department:string;}// 或者用 Partial 扩展typeUserFormInput{[KinkeyofUser]?:User[K]|;// 每个字段都可能是 };⬆ 返回目录5.2 完整示例新增/编辑表单scriptsetuplangtsimport{ref,reactive}fromvue;// 1. 列表/接口用的类型interfaceUser{id:number;name:string;age:number;department:string;}// 2. 表单类型和接口对应但允许空interfaceUserForm{name:string;age:number|;department:string;}// 3. 初始表单constformreactiveUserForm({name:,age:,department:});// 4. 编辑时接口数据 - 表单functionloadForEdit(user:User){form.nameuser.name;form.ageuser.age;form.departmentuser.department;}// 5. 提交前表单 - 接口数据functionsubmit():User|null{if(!form.name||form.age||!form.department){returnnull;}return{id:0,// 新增时name:form.name,age:Number(form.age),department:form.department};}/scripttemplateformsubmit.preventsubmitinputv-modelform.nameplaceholder姓名/inputv-model.numberform.agetypenumberplaceholder年龄/selectv-modelform.departmentoptionvalue请选择/optionoptionvalue技术部技术部/option/select/form/template要点表单类型单独定义允许、undefined提交前做校验再转成接口需要的类型⬆ 返回目录5.3 表单校验类型如 Element Plus// Form 实例类型importtype{FormInstance,FormRules}fromelement-plus;constformRefrefFormInstance();construles:FormRulesUserForm{name:[{required:true,message:请输入姓名,trigger:blur}],age:[{required:true,message:请输入年龄,trigger:blur}],department:[{required:true,message:请选择部门,trigger:change}]};asyncfunctionhandleSubmit(){if(!formRef.value)return;awaitformRef.value.validate();constdatasubmit();// 上面的 submitif(data){// 调用接口}}⬆ 返回目录六、核心规范四从 any 过渡到类型安全6.1 替代 any 的几种写法场景不要用推荐用暂时不知道类型anyunknown 类型收窄随便什么对象anyRecordstring, unknown事件对象anyEvent、MouseEvent等接口返回any定义接口类型⬆ 返回目录6.2 unknown 的正确用法// unknown 必须收窄后才能用functionhandleData(data:unknown){// ❌ data.xxx 报错unknown 不能直接访问属性// ✅ 类型收窄if(typeofdataobjectdata!nullnameindata){console.log((dataas{name:string}).name);}// ✅ 用类型守卫if(isUser(data)){console.log(data.name);// 安全}}functionisUser(val:unknown):valisUser{return(typeofvalobjectval!nullidinvalnameinval);}⬆ 返回目录6.3 泛型让类型跟着数据走// 通用的列表请求interfaceApiResponseT{code:number;data:T;message:string;}// 使用constresawaitfetchUserList();// res.data 会被推断为 User[]constusers:User[]res.data;⬆ 返回目录七、编码规范速查7.1 命名接口PascalCase如UserInfo、FormConfig类型别名PascalCase如UserForm泛型单字母或TUser等形式⬆ 返回目录7.2 组织方式// 1. 简单项目类型和组件放一起// 2. 中大型项目抽到 types/ 目录// types/user.tsexportinterfaceUser{...}exportinterfaceUserForm{...}// 组件里importtype{User,UserForm}from/types/user;⬆ 返回目录7.3 可以记一下的规则优先interface需要联合、交叉等再考虑type能用interface就别用type定义对象导出用export type或import type方便做 Tree Shaking尽量不写any实在要绕过用unknownProps、表单、接口数据结构分清楚各用各的类型⬆ 返回目录八、小结这一篇主要讲了四块接口设计用interface描述接口、列表、配置注意可选、必选和命名约定Props 类型definePropsProps()withDefaults不要直接改 props表单类型表单类型单独定义允许空值提交前再做转换和校验告别 any用unknown、泛型、类型守卫让 TS 真正发挥作用类型规范不是追求「写法炫酷」而是减少运行时报错、提高可维护性。建议从一个小模块开始先把接口、Props、表单类型统一好再慢慢扩展到整个项目。如果这篇文章对你有帮助欢迎点赞、收藏有疑问也可以在评论区留言讨论。⬆ 返回目录 系列模块导航 编码语法规范《一、JS/TS 编码规范实战Vue 场景变量 / 函数 / 类型标注避坑编码语法规范篇》《二、async/await 规范错误处理 / 避免嵌套 / 防重复请求异步代码更优雅编码语法规范篇》《三、前端 utils 工具函数规范拆分 / 命名 / 复用全指南避开全局污染等高频坑编码语法规范篇》《四、前端 console 日志规范实战高效调试 / 垃圾 log 清理与线上安全避坑编码语法规范篇》《五、JS 函数单一职责实战拆分逻辑 / 告别面条代码写出可维护团队级代码编码语法规范篇》《六、TypeScriptVue 实战告别 any 滥用统一接口 / Props / 表单类型实现类型安全编码语法规范篇》 跟着系列慢慢学把技术功底扎扎实实地打牢 系列总览前端规范实战系列目前正在持续更新中当该系列完结之后我会整理出一篇《前端规范实战系列全系列目录导航》,届时会附上文章简介以及跳转链接方便同学们按顺序体系化的学习~更新中敬请期待~⬆ 返回目录技术成长从来不是比谁写得快而是比谁写得稳、规范、可维护。哪怕每次只吃透一条规范长期下来差距会非常明显。后续我会持续更新前端规范、工程化、可维护代码相关实战干货帮你告别面条代码、维护噩梦在开发与面试中更有底气。觉得有用欢迎点赞 收藏 关注不错过每一篇实战内容。我是 Eugene与你一起写规范、写优质代码我们下篇干货见