TypeScript函数指南

19 阅读4分钟

在 TypeScript 中,函数不再是简单的代码块,而是具有精确类型约束的可组合单元。本文将深入讲解 TypeScript 函数,掌握从基础声明到多态模式的完整知识体系。

函数声明与调用

函数声明

在之前的文章中,我们介绍过 函数(Function) 也是 TypeScript 中的数据类型的一种,在 TypeScript 中,函数是这样的:

function add(a: number, b: number): number {
	return a + b;
}

注:上述代码只是为了完整的函数声明展示,实际开发中,我们只会显式声明函数的参数(上述例子中的 a 和 b);而返回类型是不用显式声明的,毕竟 TypeScript 能自己推导的事,没必要人为再操作一次。

在 TypeScript 中,支持以下五种函数声明方式。

普通具名函数

function add(a: number, b: number) {
	return a + b;
}

函数表达式

const greet = function(name: string): string {
    return `Hello, ${name}!`;
};

箭头函数

  • 简单的箭头函数语法:
const greet = (name: string): string => {
    return `Hello, ${name}!`;
};
  • 箭头函数的简写形式:
const greet = (name: string) => `Hello, ${name}!`;

类方法声明

class Calculator {
    // 实例方法
    add(x: number, y: number) {
        return x + y;
    }
    
    // 静态方法
    static multiply(x: number, y: number) {
        return x * y;
    }
    
    // 可选方法
    optionalMethod?(input: string): void;
}

构造函数声明

class User {
    constructor(
        public name: string,
        public age: number,
        private id?: string
    ) {}
}

// 构造签名类型
type UserConstructor = new (name: string, age: number) => User;

function createUser(Ctor: UserConstructor, name: string, age: number): User {
    return new Ctor(name, age);
}

函数重载

在本节开始之前,先看一条定理:

不能基于 TypeScript 来重载一个函数。

这句话到底是什么意思呢?我们来看一个简单的示例:

function add(a:number, b:number) {
  return a + b;
}

function add(a:string, b:string) {
  return a + b;
}

上述示例代码会报错:函数实现重复 ,即我们不能通过类似Java、C++ 等语言形式,通过参数类型不同来实现函数重载。那么,在 TypeScript 中,我们应该如何操作呢?

  • 为一个函数提供多个声明,但只有一个实现:
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: number | string, b: number | string): number | string {
  if (typeof a === "number" && typeof b === "number") {
    return a + b; // 数字相加
  } else if (typeof a === "string" && typeof b === "string") {
    return a + b; // 字符串拼接
  }
  throw new Error("参数必须同时为number或string");
}

上述代码中,前两个 add 函数的声明,只提供类型信息,当 TypeScript 生成 JavaScript 时,它们会自动移除,只留下实现。

上述代码只是一个简单的重载示例,本身是存在缺陷的,上述情况下,我们应优先选择条件类型,而不是重载声明。

多态函数与泛型

什么是多态

多态 顾名思义,意味着"多种形态"。在 TypeScript 中,多态主要通过泛型实现,它允许我们编写可以处理多种类型的代码,同时保持类型安全。

// 非多态版本:为每种类型写一个函数
function identityString(value: string): string {
    return value;
}

function identityNumber(value: number): number {
    return value;
}

function identityBoolean(value: boolean): boolean {
    return value;
}

// 多态版本:一个函数处理所有类型
function identity<T>(value: T): T {
    return value;
}

// 可以处理任意类型
identity("hello");   // string
identity(42);        // number
identity(true);      // boolean
identity([1, 2, 3]); // number[]

什么是泛型

泛型 是类型系统中的类型参数,它允许我们在定义函数、接口、类时声明类型变量,在使用时再指定具体类型。

// 泛型就像函数的参数,但是针对类型
// 普通函数参数:在调用时传递值
function add(x: number, y: number): number {
    return x + y;
}

// 泛型函数:在调用时传递类型
function identity<T>(value: T): T {
    return value;
}

// T 是类型参数,就像 value 是值参数

何时使用泛型?

当需要保持输入输出类型一致时

// 示例1:身份函数
function identity<T>(value: T): T {
    return value;
}

// 示例2:数组包装
function wrapInArray<T>(value: T): T[] {
    return [value];
}

// 示例3:交换函数
function swap<T, U>(pair: [T, U]): [U, T] {
    return [pair[1], pair[0]];
}

const swapped = swap([1, "hello"]); // ["hello", 1]

当函数逻辑与具体类型无关时

// 处理数组的函数,不关心数组元素的类型
function getLast<T>(array: T[]): T | undefined {
    return array[array.length - 1];
}

// 映射函数,转换逻辑由调用者提供
function mapArray<T, U>(
    array: T[],
    mapper: (item: T, index: number) => U
): U[] {
    return array.map(mapper);
}

// 过滤函数
function filterArray<T>(
    array: T[],
    predicate: (item: T) => boolean
): T[] {
    return array.filter(predicate);
}

当需要类型约束时

// 约束类型参数必须具有特定属性
interface HasLength {
    length: number;
}

function logLength<T extends HasLength>(item: T): void {
    console.log(`Length: ${item.length}`);
}

logLength("hello");      // ✅ string有length属性
logLength([1, 2, 3]);    // ✅ 数组有length属性
logLength({ length: 5 }); // ✅ 对象有length属性
// logLength(42);        // ❌ number没有length属性

泛型声明的位置与时机

在函数中声明泛型

// 单个泛型参数
function identity<T>(value: T): T {
    return value;
}

// 多个泛型参数
function pair<T, U>(first: T, second: U): [T, U] {
    return [first, second];
}

// 带约束的泛型
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}

// 默认类型参数(TypeScript 2.3+)
function createArray<T = string>(length: number, value: T): T[] {
    return Array(length).fill(value);
}

在接口中声明泛型

// 泛型接口
interface Container<T> {
    value: T;
    getValue(): T;
    setValue(value: T): void;
}

// 实现泛型接口
class NumberContainer implements Container<number> {
    value: number = 0;
    
    getValue(): number {
        return this.value;
    }
    
    setValue(value: number): void {
        this.value = value;
    }
}

// 泛型接口作为函数参数
function processContainer<T>(container: Container<T>): T {
    return container.getValue();
}

在类型别名中声明泛型

// 泛型类型别名
type Nullable<T> = T | null | undefined;
type ReadonlyRecord<K extends string, V> = { readonly [P in K]: V };
type Callback<T> = (value: T) => void;

// 使用泛型类型别名
function processValue<T>(value: T, callback: Callback<T>): void {
    callback(value);
}

const names: Nullable<string> = "Alice" || null;

在类中声明泛型

// 泛型类
class Repository<T extends { id: number }> {
    private items: T[] = [];
    
    add(item: T): void {
        this.items.push(item);
    }
    
    findById(id: number): T | undefined {
        return this.items.find(item => item.id === id);
    }
    
    getAll(): T[] {
        return [...this.items];
    }
}

// 使用泛型类
interface User {
    id: number;
    name: string;
    email: string;
}

const userRepo = new Repository<User>();
userRepo.add({ id: 1, name: "Alice", email: "alice@example.com" });
const user = userRepo.findById(1);

泛型类型推导

TypeScript 具有强大的类型推导能力,大多数情况下我们不需要显式指定泛型类型。

自动类型推导

// TypeScript会自动推导泛型类型
function identity<T>(value: T): T {
    return value;
}

// 编译器推断T为string
const result1 = identity("hello");

// 编译器推断T为number
const result2 = identity(42);

// 编译器推断T为boolean
const result3 = identity(true);

// 编译器推断T为number[]
const result4 = identity([1, 2, 3]);

部分类型推导

// 多个泛型参数时,可以部分推导
function mapArray<T, U>(
    array: T[],
    mapper: (item: T) => U
): U[] {
    return array.map(mapper);
}

// 自动推导T为number,U为string
const numbers = [1, 2, 3];
const strings = mapArray(numbers, n => n.toString());
// strings类型为string[]

推导失败的情况

// 情况1:空数组无法推导元素类型
function firstElement<T>(arr: T[]): T | undefined {
    return arr[0];
}

const empty = firstElement([]); // T被推导为unknown
// 解决方法:显式指定类型
const empty2 = firstElement<number>([]); // T为number

// 情况2:泛型约束太宽
function process<T extends string | number>(value: T): T {
    return value;
}

const result = process("hello"); // T被推导为"hello"而不是string
// 解决方法:使用类型断言
const result2 = process<string>("hello"); // T为string

泛型约束

基础约束

// 约束T必须是某种类型
function logLength<T extends { length: number }>(item: T): void {
    console.log(item.length);
}

logLength("hello");      // ✅
logLength([1, 2, 3]);    // ✅
logLength({ length: 5 }); // ✅
// logLength(42);        // ❌

// 约束多个类型参数之间的关系
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}

const user = { name: "Alice", age: 25 };
getProperty(user, "name"); // ✅
// getProperty(user, "email"); // ❌

使用类型参数约束

// 一个类型参数约束另一个
function assign<T extends U, U>(target: T, source: U): T {
    return Object.assign(target, source);
}

const target = { a: 1, b: 2 };
const source = { b: 3, c: 4 };
const result = assign(target, source); // { a: 1, b: 3, c: 4 }

// 约束类型参数为构造器
function createInstance<T>(
    Constructor: new (...args: any[]) => T,
    ...args: any[]
): T {
    return new Constructor(...args);
}

class User {
    constructor(public name: string) {}
}

const user = createInstance(User, "Alice"); // User实例

结语

本文详细介绍了TypeScript中的函数使用指南,包括函数声明与调用、函数重载、多态函数与泛型的概念,对于文章中错误的地方或者有任何问题,欢迎在评论区留言讨论!