TypeScript核心语法进阶 —— 函数与对象类型
阅读本文你能学到什么如何为函数的参数和返回值添加类型可选参数、默认参数和 void 类型的用法接口interface的定义与使用type 注解对象及与 interface 的区别前言在第二篇里我们搞定了类型注解的基础比如变量、数组这些。不过实际写项目的时候函数和对象才是每天打交道最多的。如果类型注解是基本功那今天要讲的函数和对象类型定义就算是真正的实战技能了。这篇文章不长但会把关键点都拎出来争取看完就能用上。一、函数类型注解函数类型是指给函数添加类型注解本质上就是给函数的参数和返回值添加类型约束1.1 参数与返回值类型注解语法在参数后面加: 类型在函数括号后面加: 返回值类型。// 基础用法参数和返回值都指定类型functiongreet(name:string):string{returnHello,${name}}// 多个参数functionadd(a:number,b:number):number{returnab}// 箭头函数写法constmultiply(a:number,b:number):numbera*b// 调用greet(Jack)// ✅greet(123)// ❌ 报错类型number不能赋值给类型stringadd(1,2)// ❌ 报错类型string不能赋值给类型number函数注解的好处避免因参数不对导致的函数内部逻辑错误—— 调用时必须传入正确类型的参数对函数起到说明的作用—— 看类型签名就知道函数怎么用1.2 函数表达式箭头函数函数表达式的类型注解有两种方式方式一参数和返回值分开注解constadd(a:number,b:number):numberab方式二函数整体注解只针对于函数表达式// 先看直接写法constadd:(a:number,b:number)number(a,b)ab// 更推荐把类型提取出来可以复用typecalcType(a:number,b:number)numberconstadd:calcType(a,b)abconstsub:calcType(a,b)a-bconstmult:calcType(a,b)a*bconstdiv:calcType(a,b)a/b方式二适合函数类型较复杂或需要复用的场景。1.3 可选参数可选参数表示当前参数可传可不传一旦传递实参必须保证参数类型正确。可选参数必须放在必选参数的后面。// lastName 是可选的不传也可以functiongetFullName(firstName:string,lastName?:string):string{if(lastName){return${firstName}${lastName}}returnfirstName}getFullName(Jack)// ✅ 输出JackgetFullName(Jack,Ma)// ✅ 输出Jack Ma1.4 默认参数给参数赋默认值后调用时如果不传该参数会自动使用默认值。functiongreet(name:string,title:string先生):string{return您好${title}${name}}greet(张三)// ✅ 输出您好先生 张三greet(张三,女士)// ✅ 输出您好女士 张三 小贴士有默认值的参数也建议放在参数列表的末尾让必填参数靠前提高可读性。1.5 无返回值 —— voidJS 中的有些函数只有功能没有返回值此时使用void进行返回值注解明确表示函数没有返回值。// 只做操作不返回数据functionlogMessage(msg:string):void{console.log(msg)}// 或者 return; 不返回值functionlogAndReturn(msg:string):void{console.log(msg)return// 不返回任何值}注意事项在 JS 中如果没有返回值默认返回的是undefined。但在 TS 中void和undefined不是一回事undefined在 TS 中是一种明确的简单类型。如果指定返回值为undefined那返回值必须是undefined类型。二、对象类型注解 —— 接口interface在 TS 中使用interface接口来描述对象数据的类型常用于给对象的属性和方法添加类型约束。2.1 基础用法// 定义接口interfaceUser{name:stringage:number}// 使用接口约束对象letuser:User{name:Jack,age:18}// ❌ 属性不能多也不能少letuser2:User{name:Jack// 缺少 age报错}letuser3:User{name:Jack,age:18,gender:male// ❌ 多了一个 gender报错}接口就像一份“契约”规定了对象必须有哪些属性、每个属性是什么类型属性不能多也不能少。2.2 接口的典型场景在常规业务开发中接口最典型的场景就是前后端数据通信前端向后端发送数据收集表单对象数据时的类型校验前端使用后端数据渲染后端对象数组列表时的智能提示// 模拟后端返回的用户数据interfaceUser{id:numbername:stringage:numberaddress:string}// 后端返回的用户列表constuserList:User[][{id:1,name:张三,age:20,address:北京市朝阳区},{id:2,name:李四,age:22,address:上海市浦东新区}]// 遍历渲染时编辑器会有完整的智能提示userList.forEach(item{console.log(item.name)// ✅ 自动提示 nameconsole.log(item.address)// ✅ 自动提示 address})输入 item. 时编辑器会自动提示接口中定义的所有属性及其类型如上图所示。 给新手的提醒上面的例子是理想化的写法。 实际项目中接口字段可能很多前端只定义用到的核心字段即可。非核心字段先用any或Recordstring, any过渡后续再逐步完善。2.3 可选属性和只读属性修饰符写法作用可选属性属性名?: 类型可以有也可以没有只读属性readonly 属性名: 类型只能在创建时赋值之后不能修改interfaceUser{readonlyid:number// 只读创建后不能改name:stringage:numberemail?:string// 可选可以有也可以没有}letuser:User{id:1001,name:Jack,age:18// email 不写也可以}user.emailjackexample.com// ✅ 可选属性可以后赋值// user.id 1002 // ❌ 报错只读属性不能修改readonly 常用于 ID、创建时间等一旦生成就不应该改变的字段能在编译阶段防止误修改。2.4 接口的继承接口的很多属性是可以进行类型复用的使用extends实现接口继承。// 基础接口interfacePerson{name:stringage:number}// 继承 Person新增 grade 属性interfaceStudentextendsPerson{grade:number}letstu:Student{name:小明,age:18,grade:3// ✅ 必须有}接口还可以同时继承多个接口interfaceTimestamp{createdAt:Date updatedAt:Date}interfacePerson{name:stringage:number}// User 同时继承 Person 和 TimestampinterfaceUserextendsPerson,Timestamp{email:string}constuser:User{name:Jack,age:18,email:jackexample.com,createdAt:newDate(),updatedAt:newDate()}三、type 注解对象在第二篇中我们用type定义了联合类型。其实type也可以用来定义对象结构作用和interface类似。// 使用 type 注解对象typeUser{name:stringage:numberemail?:string}letuser:User{name:Jack,age:18}type 交叉类型模拟继承类型别名配合交叉类型可以模拟继承同样可以实现类型复用。// 基础类型typePerson{name:stringage:number}// 使用交叉类型模拟继承typeStudentPerson{grade:number}letstu:Student{name:小明,age:18,grade:3}四、interface 对比 type —— 到底用哪个在实际开发中很多场景下interface和type都可以使用那到底该选哪个4.1 相同点① 都能描述对象类型// 用 interface 描述对象interfaceUserInterface{name:stringage:number}// 用 type 描述对象typeUserType{name:stringage:number}② 都能实现继承interface使用extendstype配合交叉类型// interface 用 extends 继承interfacePerson{name:string}interfaceStudentextendsPerson{grade:number}// type 用 交叉类型实现继承typePersonType{name:string}typeStudentTypePersonType{grade:number}4.2 不同点特性interfacetype描述对象✅ 可以✅ 可以描述联合类型❌ 不可以✅ 可以描述基本类型❌ 不可以✅ 可以同名合并✅ 可以属性取并集❌ 不可以会报错继承方式extends交叉类型// ✅ type 的独有能力定义联合类型和基本类型别名typeIDstring|numbertypeNamestring// ❌ type 重复声明会报错typePoint{x:number}typePoint{y:number}// ❌ 报错重复标识符// ✅ interface 可以重复声明自动合并interfacePoint{x:number}interfacePoint{y:number}// 等价于 { x: number; y: number }4.3 选择建议关于interface和type用哪个社区有两个主流观点新手入门阶段先统一用type它最灵活一个语法管所有不容易出错正式项目开发两者配合使用——用 type 定义非对象类型联合、元组、映射等用interface定义需要被类实现或需要扩展到第三方库的对象契约两者都能描述对象结构但各有所长。核心原则是用最能表达你意图的那个。五、开发中的常见错误与避坑指南常见错误正确写法说明function add(a, b): numberfunction add(a: number, b: number): number参数也要加类型不能只给返回值加function greet(name?: string, title: string)function greet(name: string, title?: string)可选参数必须在必选参数后面接口属性少写或多写严格按照接口定义赋值属性不能多也不能少混淆void和undefined无返回值用void返回undefined用undefined它们是不同的类型六、本篇小结知识点示例说明函数参数类型function fn(a: number)约束参数类型函数返回值类型function fn(): string约束返回值类型void类型function fn(): void {}无返回值的函数可选参数function fn(a?: string)参数可传可不传必须放在末尾默认参数function fn(a: string hi)不传参时使用默认值接口定义对象interface User { name: string }约束对象的形状属性不能多也不能少接口可选属性age?: number属性可有可无接口只读属性readonly id: number创建后不可修改接口继承interface Student extends Person复用接口定义接口多继承interface User extends Person, Timestamp同时继承多个接口type注解对象type User { name: string }与interface作用相似type交叉类型type Student Person { grade: number }模拟继承核心记忆口诀函数参数要注解返回值也别忘。可选参数放末尾接口对象最擅长。需要复用用继承复杂联合用 type。下篇预告 上篇回顾TypeScript 核心语法入门 —— 类型注解与类型组合本篇我们学会了函数和对象的类型注解下一篇将进入 TypeScript 的字面量类型和类型推论掌握更精确的类型定义方式让类型注解更加精准。欢迎持续关注 参考资料TypeScript 官方文档 —— Everyday TypesTypeScript 官方手册 —— 接口TypeScript 官方文档 —— 函数类型————晓说前端 · 正在从“会写”到“搞懂”的路上如果这篇文章帮到了你点个关注一起进阶。