TypeScript核心语法进阶 —— 函数与对象类型

70 阅读7分钟

阅读本文你能学到什么?

  • 如何为函数的参数和返回值添加类型
  • 可选参数、默认参数和 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. 对函数起到说明的作用 —— 看类型签名就知道函数怎么用

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 中 voidundefined 不是一回事,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 接口的典型场景

在常规业务开发中,接口最典型的场景就是前后端数据通信

  1. 前端向后端发送数据: 收集表单对象数据时的类型校验

  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. 时,编辑器会自动提示接口中定义的所有属性及其类型,如上图所示。

💡 给新手的提醒: 上面的例子是理想化的写法。 实际项目中,接口字段可能很多,前端只定义用到的核心字段即可。非核心字段先用 anyRecord<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 —— 到底用哪个?

在实际开发中,很多场景下 interfacetype 都可以使用,那到底该选哪个?

4.1 相同点

① 都能描述对象类型

// 用 interface 描述对象
interface UserInterface {
  name: string
  age: number
}

// 用 type 描述对象
type UserType = {
  name: string
  age: number
}

② 都能实现继承(interface 使用 extendstype 配合交叉类型 &

// interface 用 extends 继承
interface Person {
  name: string
}
interface Student extends Person {
  grade: number
}

// type 用 & 交叉类型实现继承
type PersonType = {
  name: string
}
type StudentType = PersonType & {
  grade: number
}

4.2 不同点

特性interfacetype
描述对象✅ 可以✅ 可以
描述联合类型❌ 不可以✅ 可以
描述基本类型❌ 不可以✅ 可以
同名合并✅ 可以(属性取并集)❌ 不可以(会报错)
继承方式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 选择建议

关于 interfacetype 用哪个,社区有两个主流观点:

  • 新手入门阶段:先统一用 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)可选参数必须在必选参数后面
接口属性少写或多写严格按照接口定义赋值属性不能多也不能少
混淆 voidundefined无返回值用 void,返回 undefinedundefined它们是不同的类型

六、本篇小结

知识点示例说明
函数参数类型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 的字面量类型和类型推论,掌握更精确的类型定义方式,让类型注解更加精准。

欢迎持续关注 🚀

参考资料

从会写到搞懂,一步一个坑。