03 - 函数

2 阅读2分钟

函数是 TypeScript 中最常用的组件。本章学习如何为函数添加完善的类型。


3.1 函数参数和返回值类型

// 基本写法:参数类型 + 返回值类型
function add(a: number, b: number): number {
  return a + b;
}

// 箭头函数
const multiply = (a: number, b: number): number => a * b;

// 返回值可以被推断,不一定要写
function greet(name: string) {
  return `Hello, ${name}!`; // TS 自动推断返回 string
}

💡 函数参数类型必须写,返回值类型通常可以省略(TS 会自动推断),但公开的 API 建议写上。


3.2 可选参数

? 标记可选参数,可选参数必须在必选参数后面:

function buildName(firstName: string, lastName?: string): string {
  if (lastName) {
    return `${firstName} ${lastName}`;
  }
  return firstName;
}

buildName("张");         // ✅ "张"
buildName("张", "三");   // ✅ "张 三"
buildName();            // ❌ 至少需要 1 个参数

3.3 默认参数

function createUser(name: string, role: string = "user"): void {
  console.log(`${name} is a ${role}`);
}

createUser("张三");          // "张三 is a user"
createUser("李四", "admin"); // "李四 is a admin"

💡 有默认值的参数不需要 ?,TypeScript 会自动将其视为可选。


3.4 剩余参数(Rest Parameters)

function sum(...numbers: number[]): number {
  return numbers.reduce((total, n) => total + n, 0);
}

sum(1, 2, 3);       // 6
sum(1, 2, 3, 4, 5); // 15

// 结合必选参数
function log(level: string, ...messages: string[]): void {
  console.log(`[${level}]`, ...messages);
}

log("INFO", "Server started", "Port: 3000");

3.5 函数类型表达式

可以用 type 或 interface 单独定义函数的类型:

// type 方式(推荐)
type MathFunc = (a: number, b: number) => number;

const add: MathFunc = (a, b) => a + b;      // 参数类型从 MathFunc 推断
const subtract: MathFunc = (a, b) => a - b;

// 作为参数类型
function calculate(a: number, b: number, operation: MathFunc): number {
  return operation(a, b);
}

calculate(10, 5, add);      // 15
calculate(10, 5, subtract); // 5

回调函数类型

type EventHandler = (event: { type: string; target: any }) => void;

function addEventListener(type: string, handler: EventHandler): void {
  // ...
}

addEventListener("click", (event) => {
  console.log(event.type); // TS 知道 event 的结构
});

3.6 函数重载

当函数根据不同参数返回不同类型时,使用重载:

// 重载签名(声明有哪些调用方式)
function format(value: string): string;
function format(value: number): string;
function format(value: Date): string;

// 实现签名(实际逻辑)
function format(value: string | number | Date): string {
  if (typeof value === "string") {
    return value.trim();
  } else if (typeof value === "number") {
    return value.toFixed(2);
  } else {
    return value.toISOString();
  }
}

format("hello ");  // ✅ string
format(3.14159);   // ✅ string
format(new Date()); // ✅ string
format(true);       // ❌ 没有匹配的重载

更精确的重载

// 根据输入类型返回不同类型
function parse(input: string): object;
function parse(input: string, asArray: true): any[];
function parse(input: string, asArray?: boolean): object | any[] {
  const result = JSON.parse(input);
  if (asArray) return Array.isArray(result) ? result : [result];
  return result;
}

const obj = parse('{"a":1}');       // 类型是 object
const arr = parse('[1,2,3]', true); // 类型是 any[]

💡 很多时候,联合类型泛型可以替代重载,代码更简洁。重载适合参数和返回值有对应关系的场景。


3.7 this 类型

interface User {
  name: string;
  greet(this: User): string; // 显式声明 this 的类型
}

const user: User = {
  name: "张三",
  greet() {
    return `Hello, ${this.name}`; // this 被推断为 User
  },
};

user.greet(); // ✅
const fn = user.greet;
fn(); // ❌ this 上下文不对

3.8 泛型函数(预览)

函数可以接受"类型参数",让它适用于多种类型:

// 不用泛型:要么用 any(不安全),要么写多个函数
function firstAny(arr: any[]): any {
  return arr[0];
}

// 用泛型:安全且灵活
function first<T>(arr: T[]): T | undefined {
  return arr[0];
}

first([1, 2, 3]);       // 返回类型:number
first(["a", "b", "c"]); // 返回类型:string
first<boolean>([true]);  // 显式指定类型参数

📖 泛型将在第 5 章详细讲解。


3.9 常见函数类型模式

异步函数

async function fetchUser(id: number): Promise<User> {
  const res = await fetch(`/api/users/${id}`);
  return res.json();
}

// 类型定义
type AsyncFn<T> = () => Promise<T>;

构造签名

interface ClockConstructor {
  new (hour: number, minute: number): ClockInterface;
}

interface ClockInterface {
  tick(): void;
}

返回 void vs undefined

// void:表示不关心返回值
type VoidFunc = () => void;

const f1: VoidFunc = () => { return true; }; // ✅ 允许!返回值被忽略
const result = f1(); // result 类型是 void,不能当 boolean 用

// undefined:必须明确返回 undefined
type UndefFunc = () => undefined;
const f2: UndefFunc = () => { return true; }; // ❌ 不能返回 boolean

📝 练习

  1. 写一个函数 repeat(str: string, times?: number),返回重复后的字符串,times 默认为 2
  2. 写一个带剩余参数的 joinWith(separator: string, ...parts: string[])
  3. 用函数重载实现 toNumber:传入 string 返回 number,传入 string[] 返回 number[]
  4. 定义一个回调类型 Comparator<T>,然后实现排序函数
// 参考答案

// 1
function repeat(str: string, times: number = 2): string {
  return str.repeat(times);
}

// 2
function joinWith(separator: string, ...parts: string[]): string {
  return parts.join(separator);
}
joinWith("-", "a", "b", "c"); // "a-b-c"

// 3
function toNumber(value: string): number;
function toNumber(value: string[]): number[];
function toNumber(value: string | string[]): number | number[] {
  if (Array.isArray(value)) {
    return value.map(Number);
  }
  return Number(value);
}

// 4
type Comparator<T> = (a: T, b: T) => number;

function sort<T>(arr: T[], compare: Comparator<T>): T[] {
  return [...arr].sort(compare);
}

sort([3, 1, 2], (a, b) => a - b); // [1, 2, 3]