【Typescript】03-函数对象与接口

函数、对象与接口

如果说基础类型只是建立了"值有边界"这件事,那么函数和对象才是 TypeScript 真正开始发挥工程价值的地方。因为现实项目里的复杂度,大部分都不是来自一个孤立的 stringnumber,而是来自"一个函数到底接收什么、返回什么""一个对象到底有哪些字段、哪些字段可以没有、哪些字段绝对不能被改"。

换句话说,真正让 TypeScript 变得有用的,不是你会不会声明一个变量的类型,而是你能不能用类型把代码中的契约说清楚。

函数类型的核心,不是语法,而是契约

先看最基础的函数类型写法:

ts 复制代码
function add(a: number, b: number): number {
  return a + b;
}

这里看起来只有两个小细节:

  • 参数类型:a: numberb: number
  • 返回值类型:: number

但从工程角度看,这已经是一份很明确的函数契约。它告诉调用方:

  • 你必须传两个数字
  • 我只会返回一个数字
  • 如果你传字符串,或者期待它返回别的结构,那是错误用法

这正是 TypeScript 在函数层面的作用。它让函数不再只是"你去读代码才知道干什么",而是签名本身就能传递大部分必要信息。

参数类型几乎总该明确写出来

局部变量可以多用推断,函数参数通常不建议省略。因为参数是边界,是外部进入当前逻辑的入口。边界模糊,后续类型链条就会一起变模糊。

ts 复制代码
function createUser(name: string, age: number) {
  return {
    id: Date.now(),
    name,
    age
  };
}

你也许会说,函数返回值这里没写类型。确实,返回值在很多简单场景里可以依赖推断。但参数最好尽量明确,因为它们直接定义了调用协议。

返回值类型什么时候该显式标注

返回值不是每次都必须手写,但在以下场景里,显式标注往往更值得:

  • 核心业务函数
  • 对外导出的公共函数
  • 返回结构复杂的函数
  • 不希望实现细节意外改变对外契约时

例如:

ts 复制代码
type CreateUserResult = {
  id: number;
  name: string;
  age: number;
};

function createUser(name: string, age: number): CreateUserResult {
  return {
    id: Date.now(),
    name,
    age
  };
}

这样做的好处是,如果某天有人把 id 改成了字符串,或者返回结构被悄悄修改,TypeScript 会立即提醒你。

可选参数、默认参数和剩余参数

可选参数

ts 复制代码
function greet(name: string, title?: string) {
  return title ? `${title} ${name}` : name;
}

title?: string 表示这个参数可以不传。注意,"可选"不是"随便乱传",它的含义是:这个位置要么不存在,要么是 string

默认参数

ts 复制代码
function createPage(pageSize: number = 10) {
  return { pageSize };
}

默认参数让函数更好用,但它本质上仍然是一个明确的输入边界,而不是放弃约束。

剩余参数

ts 复制代码
function sum(...nums: number[]): number {
  return nums.reduce((total, current) => total + current, 0);
}

剩余参数在工具函数、事件处理、转发函数里很常见。关键点是:即便参数个数可变,元素类型依然应该清楚。

对象类型是业务建模的真正起点

大部分业务代码,最终都绕不开对象。用户、订单、配置、组件 props、接口响应,本质上几乎都是对象结构。

ts 复制代码
const user: {
  id: number;
  name: string;
  active: boolean;
} = {
  id: 1,
  name: "Alice",
  active: true
};

这种内联写法在小范围里没问题,但一旦结构重复出现,就应该提取出来。原因很简单:重复结构意味着重复维护,而重复维护迟早会失控。

typeinterface 都能描述对象,但思维略有不同

你可以这样写:

ts 复制代码
type User = {
  id: number;
  name: string;
  active: boolean;
};

也可以这样写:

ts 复制代码
interface User {
  id: number;
  name: string;
  active: boolean;
}

初学阶段,两者最重要的区别不是语法能力,而是使用语义:

  • interface 更像"对象契约"
  • type 更像"类型表达式的别名"

如果你描述的是一个清晰的对象结构,interface 很自然;如果你要组合联合类型、函数类型、元组、映射类型,type 往往更灵活。

在没有团队规范时,我个人更建议:

  • 对象边界优先用 interface
  • 组合类型、工具型类型优先用 type

这不是绝对规则,但这种分工通常更利于阅读。

函数类型也应该被当成一等公民

很多项目里,函数不只是"实现逻辑",它本身也经常作为参数、配置项或策略注入出现。这时,函数类型就很值得抽离:

ts 复制代码
type Formatter = (value: string) => string;

const upperCase: Formatter = (value) => value.toUpperCase();
const trimText: Formatter = (value) => value.trim();

你会在这些场景里频繁见到这种写法:

  • 数组方法回调
  • 表单校验器
  • 组件事件回调
  • 中间件
  • 策略模式

当一个函数类型会被复用,单独给它命名,比反复写长签名清晰得多。

可选属性和只读属性,是表达业务语义的重要方式

ts 复制代码
interface Product {
  readonly id: number;
  name: string;
  description?: string;
}

这里的两个小语法,实际项目里非常有价值。

readonly

readonly 不是为了防止程序员手滑,它表达的是业务语义:这个值一旦生成,就不应该被重新赋值。比如数据库主键、创建时间、订单编号,这些字段很适合只读。

?

可选属性不是"这个字段可以乱来",而是"这个字段在合法数据里允许不存在"。这对于头像、备注、简介、扩展信息等很常见。

一个类型写得好不好,很大程度上就看这些边界有没有说清楚。

一个更接近真实项目的例子

ts 复制代码
interface UserProfile {
  readonly id: number;
  name: string;
  email: string;
  avatar?: string;
  bio?: string;
}

function updateUserName(user: UserProfile, nextName: string): UserProfile {
  return {
    ...user,
    name: nextName
  };
}

从这个例子里你应该能感受到,类型系统的意义不是限制你,而是帮你把对象的合法形态表达得更稳定:

  • id 不能乱改
  • nameemail 是核心字段
  • avatarbio 可以没有

这类结构一旦清楚,后面的函数、接口、组件都更容易围绕它工作。

一个工程判断标准:类型有没有把业务意图说出来

写 TypeScript 时,别只问"这样能不能通过编译",还要问:

  • 这个字段是否应该只读
  • 这个属性是否真的可选
  • 这个函数的参数是不是表达了真实前提
  • 这个返回值是不是把外部契约说清楚了

如果你的类型只是"勉强让编辑器不报错",那它的价值会很有限。真正好的类型,是读者不看实现细节,也能大概理解这段代码能做什么、不能做什么。

常见误区

误区一:对象一复杂就直接 any

这会让最该被建模的部分恰好失去保护。复杂对象更应该拆清楚,不该直接逃避。

误区二:所有返回值都不写类型

简单局部函数可以省,但公共函数和核心逻辑如果完全依赖推断,后续实现变化时更容易无意中破坏契约。

误区三:把 typeinterface 之争当成重点

真正重要的不是你站哪一派,而是你能不能稳定地写出清晰的结构定义。语义一致、团队一致,通常比"理论上谁更优雅"更重要。

本文小结

函数、对象与接口,是 TypeScript 建模的主战场。基础类型解决的是"一个值是什么",函数和对象解决的是"系统如何协作、边界如何表达"。你如果能把函数契约写清楚、把对象结构建模准确,就已经掌握了 TypeScript 最有实际价值的一部分。

练习

  1. 给一个"创建用户"的函数补上参数类型和返回值类型,并思考哪些字段应该由调用方传入,哪些字段应该由系统生成。
  2. interface 定义一个 Book,包含只读 id、必填 title、可选 author、可选 description
  3. 写一个函数类型 Validator,表示接收字符串并返回布尔值,然后实现两个不同的校验函数。

后记

2026年5月21日于上海。

相关推荐
weixin_437918961 小时前
前端String 数组和Math API大全
前端·javascript
threelab1 小时前
Three.js 银河星系效果 | 三维可视化 / AI 提示词
开发语言·javascript·人工智能
程序员敲代码吗1 小时前
探索JavaScript对象创建的灵活方式
开发语言·javascript·ecmascript
海上彼尚1 小时前
Nodejs也能写Agent - 7.基础篇 - MCP
前端·javascript·人工智能·node.js
FlyWIHTSKY1 小时前
Next.js中客户端组件和服务端组件
开发语言·javascript·ecmascript
天若有情6731 小时前
轻量级状态事件总线 eventbusx-js 开源使用教程
开发语言·javascript·npm·开源·事件·事件总线
李剑一1 小时前
我开发了一款防止摸鱼被发现的工具,现已开源
前端
启山智软1 小时前
从零搭建商城系统前端:技术选型与核心架构实践
前端·架构
ZC跨境爬虫1 小时前
跟着 MDN 学CSS day_5:掌握属性选择器的存否匹配与子字符串匹配
前端·javascript·css·ui·html