在 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中的函数使用指南,包括函数声明与调用、函数重载、多态函数与泛型的概念,对于文章中错误的地方或者有任何问题,欢迎在评论区留言讨论!