函数基础:参数和返回类型
欢迎继续本专栏的第九篇文章。在前几期中,我们已逐步深化了对 TypeScript 类型系统的认识,包括基本类型、特殊类型、枚举、类型断言,以及数组、元组和对象的管理。今天,我们将转向函数这一核心构建块,重点探讨函数的基础知识,特别是类型签名、参数和返回类型的定义。这部分内容是理解 TypeScript 如何提升函数可靠性的关键。我们将从函数的基本概念入手,逐步引入参数类型、返回类型、可选参数、默认参数和函数重载的概念,并通过丰富示例和实际场景分析,帮助您编写更健壮的函数代码。内容将由浅入深展开,确保您能从简单示例过渡到复杂应用,同时获得深刻的洞见。
理解函数在 TypeScript 中的定位
在编程中,函数是代码复用和逻辑封装的基本单位。JavaScript 中的函数灵活但动态,容易因参数类型不匹配或返回意外值而引发运行时错误。TypeScript 通过引入类型签名(type signature),为函数添加了静态检查层,这让函数成为类型安全的堡垒。类型签名本质上是函数的"合同":它定义了输入(参数类型)、输出(返回类型)和行为约束。
为什么函数类型如此重要?在大型项目中,函数往往被多处调用。没有类型,修改一个函数可能引发连锁 bug;有了类型,编译器能在变更时立即反馈。根据 TypeScript 社区的经验,使用函数类型能将相关错误减少 20-30%。函数在 TypeScript 中的定位不仅是执行代码,更是类型系统的桥梁:它连接变量、对象和更高级结构如类和泛型(后续文章详述)。
TypeScript 函数类型借鉴了函数式语言如 Haskell 的理念,但保持与 JavaScript 的兼容。任何 JS 函数都是有效的 TS 函数,但添加类型能带来智能提示、重构支持和文档化。需要注意的是,函数类型在运行时被移除,不会影响性能。我们将从最简单的函数签名开始,逐步扩展到高级特性,确保您能逐步掌握如何编写可靠的函数代码。
函数的基本定义与类型签名
让我们从函数的基础语法入手。TypeScript 函数的定义类似于 JavaScript,但添加了类型注解。
函数声明的基本形式
一个简单的无参数函数:
typescript
function greet(): void {
console.log("Hello, TypeScript!");
}
这里,(): void 是类型签名:空参数,返回 void(无值)。调用 greet() 时,编译器确保无参数传入。
带参数的函数:
typescript
function add(a: number, b: number): number {
return a + b;
}
签名 (a: number, b: number): number 指定两个 number 参数,返回 number。如果调用 add("1", 2),编译器报错:字符串不可赋值为 number。
函数表达式类似:
typescript
const multiply: (x: number, y: number) => number = (x, y) => x * y;
这里,类型签名作为变量类型,箭头函数体匹配它。
类型签名的必要性在于它充当文档:阅读者一眼知函数需求。同时,IDE 如 VS Code 提供参数提示,提升开发效率。
类型签名的组成部分
类型签名包括:
-
参数列表:每个参数的名称和类型,如 a: number。
-
返回类型:函数输出的类型,如 : number。
-
可选的函数类型:用于变量或参数,如 (param: string) => boolean。
在 tsconfig.json 的 strict 模式下,未指定返回类型会推断,但显式声明推荐用于清晰性。
简单示例扩展:考虑一个处理字符串的函数。
typescript
function capitalize(text: string): string {
return text.charAt(0).toUpperCase() + text.slice(1);
}
如果返回非 string,如 number,编译错误。这确保函数行为一致。
通过这些基础,您可以看到类型签名如何将函数从"黑盒"转为"透明合同"。
参数类型:确保输入的安全性
参数是函数的输入,TypeScript 通过类型注解锁定它们,防止无效数据进入。
基本参数类型
参数类型直接注解在名称后。
typescript
function describePerson(name: string, age: number): string {
return `${name} is ${age} years old.`;
}
调用 describePerson("Alice", 30) 有效;describePerson(30, "Alice") 报错:参数顺序和类型必须匹配。
多参数场景:
typescript
function calculateArea(length: number, width: number, unit: string = "sq ft"): string {
return `${length * width} ${unit}`;
}
这里引入默认值(稍后详述),但类型仍指定。
参数类型支持联合:
typescript
function logValue(value: string | number): void {
console.log(value);
}
这允许灵活输入,但内部需处理类型(用 typeof 守卫)。
参数类型的深入应用
在复杂参数中,用接口定义形状。
typescript
interface Point {
x: number;
y: number;
}
function distance(p1: Point, p2: Point): number {
return Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2);
}
这在几何计算中实用,确保参数有正确属性。
数组参数:
typescript
function sumArray(numbers: number[]): number {
return numbers.reduce((acc, curr) => acc + curr, 0);
}
防止传入非数组或混合类型。
参数类型的益处:及早错误检测。在团队中,它减少沟通:函数签名即规格。
陷阱:参数过多表示需重构为对象。
返回类型:定义输出的预期
返回类型指定函数输出,确保调用者得到预期值。
基本返回类型
如 add 示例,返回 number。
无返回用 void:
typescript
function logError(message: string): void {
console.error(message);
}
如果添加 return,编译错误,除非 return undefined(但不推荐)。
推断返回:TypeScript 可自动推断,但显式更好。
typescript
function getLength(text: string) { // 推断 :string
return text.length; // 错误:返回 number,但推断为 number
}
修正为 : number。
返回类型的深入应用
返回联合:
typescript
function findItem(id: number): string | undefined {
// 逻辑
if (found) return "item";
return undefined;
}
这处理可选结果。
返回 Promise:
typescript
async function fetchData(url: string): Promise<{ data: string }> {
const res = await fetch(url);
return { data: await res.text() };
}
在异步中,确保类型匹配。
返回类型提升可靠性:调用者知输出,可安全链式调用。
高级:返回 never 用于不返回函数(如 throw)。
可选参数:处理灵活输入
可选参数允许函数接受或忽略某些输入,用 ? 标记。
可选参数的基本用法
typescript
function greetUser(name: string, title?: string): string {
return title ? `${title} ${name}` : name;
}
调用 greetUser("Alice") 或 greetUser("Alice", "Ms.") 有效。title 默认为 undefined。
位置重要:可选参数后不能有必选。
typescript
// function bad(a?: number, b: number): void {} // 错误
正确:必选在前。
可选参数的深入应用
结合默认值:
typescript
function createUser(name: string, age?: number): { name: string; age: number | undefined } {
return { name, age };
}
在 API 中:
typescript
interface Options {
timeout?: number;
retries?: number;
}
function request(url: string, options?: Options): Promise<Response> {
// 实现
}
这允许灵活配置。
可选参数与守卫:
内部检查:
typescript
if (title !== undefined) { /* 使用 */ }
可选参数增加函数通用性,但过多可选需考虑重载(后述)。
风险:undefined 处理不当导致 bug。总是考虑默认行为。
默认参数:提供内置值
默认参数在 ES6 引入,TypeScript 支持并类型化。
默认参数的基本用法
typescript
function multiply(a: number, b: number = 1): number {
return a * b;
}
调用 multiply(5) 返回 5;multiply(5, 2) 返回 10。
类型从默认值推断,但可显式。
默认参数的深入应用
复杂默认:
typescript
function buildQuery(params: { key: string; value: string }[] = []): string {
return params.map(p => `${p.key}=${p.value}`).join("&");
}
函数默认:
typescript
function process(data: string, transformer: (s: string) => string = s => s.toUpperCase()): string {
return transformer(data);
}
这在管道处理中实用。
默认与可选结合:默认使可选更强大。
typescript
function log(message: string, level: string = "info"): void {
// ...
}
默认参数简化调用,减少 boilerplate。但默认值需简单,避免运行时副作用。
陷阱:默认在调用时求值,非定义时。
函数重载:处理多种签名
函数重载允许同一函数名有多个签名,基于参数选择实现。
函数重载的基本用法
重载签名在实现前定义。
typescript
function combine(a: string, b: string): string;
function combine(a: number, b: number): number;
function combine(a: string | number, b: string | number): string | number {
if (typeof a === "string" && typeof b === "string") {
return a + b;
} else if (typeof a === "number" && typeof b === "number") {
return a + b;
}
throw new Error("Invalid types");
}
调用 combine("a", "b") 返回 string;combine(1, 2) 返回 number。IDE 基于参数提示返回。
重载签名不实现,仅类型;实现签名覆盖所有。
函数重载的深入应用
多参数重载:
typescript
function format(value: string): string;
function format(value: number, decimals: number): string;
function format(value: string | number, decimals?: number): string {
if (typeof value === "string") {
return value.trim();
} else {
return value.toFixed(decimals ?? 2);
}
}
这处理不同输入。
类方法重载类似。
箭头函数不支持直接重载,用函数声明或接口。
接口重载:
typescript
interface Overloaded {
(a: string): string;
(a: number): number;
}
const func: Overloaded = (a: any) => a;
重载提升函数多态性,在库设计中常见,如 lodash。
风险:实现复杂,易出错。优先联合类型;重载用于返回不同。
实际应用:编写可靠函数代码
整合概念,构建实用函数。
场景1:数据处理函数
typescript
function processData(data: unknown, validator?: (d: unknown) => boolean): string | never {
if (validator && !validator(data)) {
throw new Error("Invalid data");
}
return data as string; // 断言后返回
}
场景2:配置函数
typescript
interface Config {
host: string;
port?: number;
}
function connect(config: Config, timeout: number = 5000): void {
// 连接逻辑
}
在 web app 中,可选 port 默认 80。
案例研究
在 Node.js API,函数重载处理查询:
重载让 API 灵活。
在 React hook,重载自定义 hook。
这些应用展示函数类型如何使代码可靠。
高级用法:扩展函数能力
this 类型
在对象方法:
typescript
interface Logger {
log: (message: string) => void;
}
const consoleLogger: Logger = {
log(this: Logger, message) { // this 类型
console.log(message);
}
};
Rest 参数
typescript
function sum(...numbers: number[]): number {
return numbers.reduce((a, b) => a + b, 0);
}
参数解构
typescript
function point({ x, y }: { x: number; y: number }): number {
return x + y;
}
高级:重载与泛型结合(后文)。
风险与最佳实践
风险:
- 类型宽松导致 bug。
- 重载实现不覆盖所有。
- 默认值副作用。
实践:
- 总是指定返回。
- 用接口参数复杂结构。
- 测试边缘。
- 文档签名。
遵循这些,函数更可靠。
结语:函数,类型安全的基石
通过本篇文章的详尽探讨,您已掌握函数基础,从签名到重载。这些知识将助您编写更可靠代码。实践:在项目添加类型。下一期探讨箭头函数与 this,敬请期待。若有疑问,欢迎交流。我们将继续前行。