阅读本文你能学到什么?
- 如何为函数的参数和返回值添加类型
- 可选参数、默认参数和 void 类型的用法
- 接口(interface)的定义与使用
- type 注解对象及与 interface 的区别
前言
在第二篇里,我们搞定了类型注解的基础,比如变量、数组这些。不过实际写项目的时候,函数和对象才是每天打交道最多的。
如果类型注解是基本功,那今天要讲的函数和对象类型定义,就算是真正的实战技能了。这篇文章不长,但会把关键点都拎出来,争取看完就能用上。
一、函数类型注解
函数类型是指给函数添加类型注解,本质上就是给函数的参数和返回值添加类型约束
1.1 参数与返回值类型注解
语法:在参数后面加: 类型,在函数括号后面加: 返回值类型。
// 基础用法:参数和返回值都指定类型functiongreet(name:string):string{return`Hello,${name}`}// 多个参数functionadd(a:number,b:number):number{returna+b}// 箭头函数写法constmultiply=(a:number,b:number):number=>a*b// 调用greet('Jack')// ✅greet(123)// ❌ 报错:类型"number"不能赋值给类型"string"add(1,'2')// ❌ 报错:类型"string"不能赋值给类型"number"函数注解的好处:
避免因参数不对导致的函数内部逻辑错误—— 调用时必须传入正确类型的参数
对函数起到说明的作用—— 看类型签名就知道函数怎么用
1.2 函数表达式(箭头函数)
函数表达式的类型注解有两种方式:
方式一:参数和返回值分开注解
constadd=(a:number,b:number):number=>a+b方式二:函数整体注解(只针对于函数表达式)
// 先看直接写法constadd:(a:number,b:number)=>number=(a,b)=>a+b// 更推荐:把类型提取出来,可以复用typecalcType=(a:number,b:number)=>numberconstadd:calcType=(a,b)=>a+bconstsub: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 无返回值 —— void
JS 中的有些函数只有功能没有返回值,此时使用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或Record<string, any>过渡,后续再逐步完善。
2.3 可选属性和只读属性
| 修饰符 | 写法 | 作用 |
|---|---|---|
| 可选属性 | 属性名?: 类型 | 可以有,也可以没有 |
| 只读属性 | readonly 属性名: 类型 | 只能在创建时赋值,之后不能修改 |
interfaceUser{readonlyid:number// 只读:创建后不能改name:stringage:numberemail?:string// 可选:可以有也可以没有}letuser:User={id:1001,name:'Jack',age:18// email 不写也可以}user.email='jack@example.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:'jack@example.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}// 使用交叉类型模拟继承typeStudent=Person&{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使用extends,type配合交叉类型&)
// interface 用 extends 继承interfacePerson{name:string}interfaceStudentextendsPerson{grade:number}// type 用 & 交叉类型实现继承typePersonType={name:string}typeStudentType=PersonType&{grade:number}4.2 不同点
| 特性 | interface | type |
|---|---|---|
| 描述对象 | ✅ 可以 | ✅ 可以 |
| 描述联合类型 | ❌ 不可以 | ✅ 可以 |
| 描述基本类型 | ❌ 不可以 | ✅ 可以 |
| 同名合并 | ✅ 可以(属性取并集) | ❌ 不可以(会报错) |
| 继承方式 | extends | &(交叉类型) |
// ✅ type 的独有能力:定义联合类型和基本类型别名typeID=string|numbertypeName=string// ❌ 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): number | function 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 Types
- TypeScript 官方手册 —— 接口
- TypeScript 官方文档 —— 函数类型
————
晓说前端 · 正在从“会写”到“搞懂”的路上
如果这篇文章帮到了你,点个关注,一起进阶。