阅读本文你能学到什么?
- 如何为函数的参数和返回值添加类型
- 可选参数、默认参数和 void 类型的用法
- 接口(interface)的定义与使用
- type 注解对象及与 interface 的区别
前言
在第二篇里,我们搞定了类型注解的基础,比如变量、数组这些。不过实际写项目的时候,函数和对象才是每天打交道最多的。
如果类型注解是基本功,那今天要讲的函数和对象类型定义,就算是真正的实战技能了。这篇文章不长,但会把关键点都拎出来,争取看完就能用上。
一、函数类型注解
函数类型是指给函数添加类型注解,本质上就是给函数的参数和返回值添加类型约束
1.1 参数与返回值类型注解
语法: 在参数后面加 : 类型,在函数括号后面加 : 返回值类型。
// 基础用法:参数和返回值都指定类型
function greet(name: string): string {
return `Hello, ${name}`
}
// 多个参数
function add(a: number, b: number): number {
return a + b
}
// 箭头函数写法
const multiply = (a: number, b: number): number => a * b
// 调用
greet('Jack') // ✅
greet(123) // ❌ 报错:类型"number"不能赋值给类型"string"
add(1, '2') // ❌ 报错:类型"string"不能赋值给类型"number"
函数注解的好处:
-
避免因参数不对导致的函数内部逻辑错误 —— 调用时必须传入正确类型的参数
-
对函数起到说明的作用 —— 看类型签名就知道函数怎么用
1.2 函数表达式(箭头函数)
函数表达式的类型注解有两种方式:
方式一:参数和返回值分开注解
const add = (a: number, b: number): number => a + b
方式二:函数整体注解(只针对于函数表达式)
// 先看直接写法
const add: (a: number, b: number) => number = (a, b) => a + b
// 更推荐:把类型提取出来,可以复用
type calcType = (a: number, b: number) => number
const add: calcType = (a, b) => a + b
const sub: calcType = (a, b) => a - b
const mult: calcType = (a, b) => a * b
const div: calcType = (a, b) => a / b
方式二适合函数类型较复杂或需要复用的场景。
1.3 可选参数
可选参数表示当前参数可传可不传,一旦传递实参必须保证参数类型正确。可选参数必须放在必选参数的后面。
// lastName 是可选的,不传也可以
function getFullName(firstName: string, lastName?: string): string {
if (lastName) {
return `${firstName} ${lastName}`
}
return firstName
}
getFullName('Jack') // ✅ 输出:Jack
getFullName('Jack', 'Ma') // ✅ 输出:Jack Ma
1.4 默认参数
给参数赋默认值后,调用时如果不传该参数,会自动使用默认值。
function greet(name: string, title: string = '先生'): string {
return `您好,${title} ${name}`
}
greet('张三') // ✅ 输出:您好,先生 张三
greet('张三', '女士') // ✅ 输出:您好,女士 张三
💡 小贴士:有默认值的参数也建议放在参数列表的末尾,让必填参数靠前,提高可读性。
1.5 无返回值 —— void
JS 中的有些函数只有功能没有返回值,此时使用 void 进行返回值注解,明确表示函数没有返回值。
// 只做操作,不返回数据
function logMessage(msg: string): void {
console.log(msg)
}
// 或者 return; 不返回值
function logAndReturn(msg: string): void {
console.log(msg)
return // 不返回任何值
}
注意事项: 在 JS 中如果没有返回值,默认返回的是
undefined。但在 TS 中void和undefined不是一回事,undefined在 TS 中是一种明确的简单类型。如果指定返回值为undefined,那返回值必须是undefined类型。
二、对象类型注解 —— 接口(interface)
在 TS 中使用 interface(接口) 来描述对象数据的类型,常用于给对象的属性和方法添加类型约束。
2.1 基础用法
// 定义接口
interface User {
name: string
age: number
}
// 使用接口约束对象
let user: User = {
name: 'Jack',
age: 18
}
// ❌ 属性不能多也不能少
let user2: User = {
name: 'Jack'
// 缺少 age,报错!
}
let user3: User = {
name: 'Jack',
age: 18,
gender: 'male' // ❌ 多了一个 gender,报错!
}
接口就像一份“契约”,规定了对象必须有哪些属性、每个属性是什么类型,属性不能多也不能少。
2.2 接口的典型场景
在常规业务开发中,接口最典型的场景就是前后端数据通信:
-
前端向后端发送数据: 收集表单对象数据时的类型校验
-
前端使用后端数据: 渲染后端对象数组列表时的智能提示
// 模拟后端返回的用户数据
interface User {
id: number
name: string
age: number
address: string
}
// 后端返回的用户列表
const userList: User[] = [
{ id: 1, name: '张三', age: 20, address: '北京市朝阳区' },
{ id: 2, name: '李四', age: 22, address: '上海市浦东新区' }
]
// 遍历渲染时,编辑器会有完整的智能提示
userList.forEach(item => {
console.log(item.name) // ✅ 自动提示 name
console.log(item.address) // ✅ 自动提示 address
})
输入 item. 时,编辑器会自动提示接口中定义的所有属性及其类型,如上图所示。
💡 给新手的提醒: 上面的例子是理想化的写法。 实际项目中,接口字段可能很多,前端只定义用到的核心字段即可。非核心字段先用
any或Record<string, any>过渡,后续再逐步完善。
2.3 可选属性和只读属性
| 修饰符 | 写法 | 作用 |
|---|---|---|
| 可选属性 | 属性名?: 类型 | 可以有,也可以没有 |
| 只读属性 | readonly 属性名: 类型 | 只能在创建时赋值,之后不能修改 |
interface User {
readonly id: number // 只读:创建后不能改
name: string
age: number
email?: string // 可选:可以有也可以没有
}
let user: User = {
id: 1001,
name: 'Jack',
age: 18
// email 不写也可以
}
user.email = 'jack@example.com' // ✅ 可选属性可以后赋值
// user.id = 1002 // ❌ 报错:只读属性不能修改
readonly 常用于 ID、创建时间等一旦生成就不应该改变的字段,能在编译阶段防止误修改。
2.4 接口的继承
接口的很多属性是可以进行类型复用的,使用 extends 实现接口继承。
// 基础接口
interface Person {
name: string
age: number
}
// 继承 Person,新增 grade 属性
interface Student extends Person {
grade: number
}
let stu: Student = {
name: '小明',
age: 18,
grade: 3 // ✅ 必须有
}
接口还可以同时继承多个接口:
interface Timestamp {
createdAt: Date
updatedAt: Date
}
interface Person {
name: string
age: number
}
// User 同时继承 Person 和 Timestamp
interface User extends Person, Timestamp {
email: string
}
const user: User = {
name: 'Jack',
age: 18,
email: 'jack@example.com',
createdAt: new Date(),
updatedAt: new Date()
}
三、type 注解对象
在第二篇中,我们用 type 定义了联合类型。其实 type 也可以用来定义对象结构,作用和 interface 类似。
// 使用 type 注解对象
type User = {
name: string
age: number
email?: string
}
let user: User = {
name: 'Jack',
age: 18
}
type + 交叉类型模拟继承
类型别名配合交叉类型(&)可以模拟继承,同样可以实现类型复用。
// 基础类型
type Person = {
name: string
age: number
}
// 使用交叉类型模拟继承
type Student = Person & {
grade: number
}
let stu: Student = {
name: '小明',
age: 18,
grade: 3
}
四、interface 对比 type —— 到底用哪个?
在实际开发中,很多场景下 interface 和 type 都可以使用,那到底该选哪个?
4.1 相同点
① 都能描述对象类型
// 用 interface 描述对象
interface UserInterface {
name: string
age: number
}
// 用 type 描述对象
type UserType = {
name: string
age: number
}
② 都能实现继承(interface 使用 extends,type 配合交叉类型 &)
// interface 用 extends 继承
interface Person {
name: string
}
interface Student extends Person {
grade: number
}
// type 用 & 交叉类型实现继承
type PersonType = {
name: string
}
type StudentType = PersonType & {
grade: number
}
4.2 不同点
| 特性 | interface | type |
|---|---|---|
| 描述对象 | ✅ 可以 | ✅ 可以 |
| 描述联合类型 | ❌ 不可以 | ✅ 可以 |
| 描述基本类型 | ❌ 不可以 | ✅ 可以 |
| 同名合并 | ✅ 可以(属性取并集) | ❌ 不可以(会报错) |
| 继承方式 | extends | &(交叉类型) |
// ✅ type 的独有能力:定义联合类型和基本类型别名
type ID = string | number
type Name = string
// ❌ type 重复声明会报错
type Point = { x: number }
type Point = { y: number } // ❌ 报错:重复标识符
// ✅ interface 可以重复声明,自动合并
interface Point {
x: number
}
interface Point {
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 的字面量类型和类型推论,掌握更精确的类型定义方式,让类型注解更加精准。
欢迎持续关注 🚀
参考资料
从会写到搞懂,一步一个坑。