第6章 函数与泛型:打造灵活的"类型安全工具"
想象你正在经营一家精密工具制造厂——函数(Function) 就是你的专业工具,而泛型(Generics) 则是让这些工具变得万能的神奇配件。TypeScript的函数与泛型系统让你能创建既类型安全又高度可复用的代码组件。这一章,我们将学会如何打造属于自己的"类型安全工具箱"!
6.1 函数类型定义——给函数戴上"类型手套"
函数是代码世界的基本工具,TypeScript为这些工具提供了精确的类型规格说明书。
🔧 基础函数类型定义
// 1. 基本函数类型 - 就像工具的使用说明书
function add(x: number, y: number): number {
return x + y;
}
// 2. 箭头函数类型 - 现代简洁语法
const multiply = (a: number, b: number): number => a * b;
// 3. 函数表达式 - 完整类型声明
const divide: (dividend: number, divisor: number) => number
= function(d, ds) { return d / ds; };
// 使用示例
console.log(add(2, 3)); // 5
console.log(multiply(4, 5)); // 20
console.log(divide(10, 2)); // 5
// 错误示例:类型不匹配
// add("2", 3); // 错误:参数类型不匹配
// divide(10); // 错误:参数数量不足
💡 类型安全优势:
- 参数保护:防止错误类型的参数传入
- 返回值保证:确保返回值符合预期类型
- 自文档化:函数签名即使用文档
- IDE支持:提供智能提示和错误检查
🎯 函数类型的组成要素
- 参数类型:定义输入数据的类型规范
- 返回类型:指定输出结果的类型
- 函数签名:完整的类型契约描述
- 类型推断:TypeScript的智能类型判断
6.2 可选参数与默认参数——灵活的函数"配置项"
就像瑞士军刀有多种工具配置一样,函数也需要灵活的参数配置来适应不同场景。
🔧 可选参数:使用 ? 标记
// 用户创建函数 - email是可选的
function createUser(name: string, email?: string) {
return {
name,
email: email || "未提供"
};
}
// 使用示例
console.log(createUser("张三"));
// { name: "张三", email: "未提供" }
console.log(createUser("李四", "lisi@example.com"));
// { name: "李四", email: "lisi@example.com" }
// 个人资料函数 - 多个可选参数
function createProfile(name: string, age?: number, city?: string) {
const profile: any = { name };
if (age !== undefined) profile.age = age;
if (city) profile.city = city;
return profile;
}
console.log(createProfile("王五"));
// { name: "王五" }
console.log(createProfile("赵六", 25, "北京"));
// { name: "赵六", age: 25, city: "北京" }
⚙️ 默认参数:ES6语法增强
// 订单创建函数 - 数量有默认值
function createOrder(product: string, quantity: number = 1) {
console.log(`创建订单:${product} × ${quantity}`);
return { product, quantity, total: quantity * 100 };
}
createOrder("笔记本电脑"); // "创建订单:笔记本电脑 × 1"
createOrder("鼠标", 2); // "创建订单:鼠标 × 2"
// 网络连接函数 - 多个默认参数
function connect(
host: string,
port: number = 8080,
protocol: "http" | "https" = "https"
) {
const url = `${protocol}://${host}:${port}`;
console.log(`连接到:${url}`);
return url;
}
console.log(connect("example.com"));
// "连接到:https://example.com:8080"
// 返回:"https://example.com:8080"
console.log(connect("api.example.com", 3000, "http"));
// "连接到:http://api.example.com:3000"
// 返回:"http://api.example.com:3000"
// 配置对象默认值
interface ServerConfig {
host: string;
port?: number;
ssl?: boolean;
}
function startServer(config: ServerConfig = { host: "localhost", port: 3000, ssl: false }) {
const { host, port = 3000, ssl = false } = config;
console.log(`服务器启动:${ssl ? 'https' : 'http'}://${host}:${port}`);
}
startServer(); // "服务器启动:http://localhost:3000"
startServer({ host: "0.0.0.0", port: 8080, ssl: true });
// "服务器启动:https://0.0.0.0:8080"
⚠️ 重要规则:可选参数和带默认值的参数必须放在必需参数之后
6.3 剩余参数——收集参数的"魔法口袋"
剩余参数就像一个神奇的收纳袋,能将多个参数整齐地收集到一个数组中。
📦 剩余参数的强大功能
// 1. 数学计算函数 - 处理任意数量的数字
function sum(...numbers: number[]): number {
const result = numbers.reduce((total, num) => total + num, 0);
console.log(`计算 ${numbers.join(' + ')} = ${result}`);
return result;
}
console.log(sum(1, 2, 3));
// "计算 1 + 2 + 3 = 6"
// 返回:6
console.log(sum(10, 20, 30, 40));
// "计算 10 + 20 + 30 + 40 = 100"
// 返回:100
// 2. HTML标签构建器 - 与其他参数结合
function buildTag(tagName: string, ...classes: string[]) {
const classAttr = classes.length > 0 ? ` class="${classes.join(' ')}"` : '';
const result = `<${tagName}${classAttr}></${tagName}>`;
console.log(`生成标签:${result}`);
return result;
}
console.log(buildTag("div", "container", "active"));
// "生成标签:<div class="container active"></div>"
// 返回:"<div class="container active"></div>"
console.log(buildTag("span"));
// "生成标签:<span></span>"
// 返回:"<span></span>"
// 3. 日志记录系统 - 类型安全示例
function logMessages(level: "info" | "warn" | "error", ...messages: string[]) {
const timestamp = new Date().toISOString();
messages.forEach(message => {
console.log(`[${timestamp}] ${level.toUpperCase()}: ${message}`);
});
}
logMessages("info", "系统启动", "数据库连接成功");
// "[2024-01-01T12:00:00.000Z] INFO: 系统启动"
// "[2024-01-01T12:00:00.000Z] INFO: 数据库连接成功"
logMessages("error", "连接超时");
// "[2024-01-01T12:00:00.000Z] ERROR: 连接超时"
// 错误示例:类型不符
// logMessages("info", "消息", 123); // 错误!数字不能赋值给string
🎯 剩余参数的应用场景
- 数学运算:处理不定数量的数值
- 字符串拼接:组合多个文本片段
- 事件处理:传递多个回调函数
- 配置合并:整合多个配置对象
6.4 函数重载——同一函数的"多副面孔"
函数重载就像一个多才多艺的演员,能根据不同的"剧本"(参数)展现不同的"表演"(行为)。
🎭 重载签名:定义多种调用方式
// 数据获取函数 - 支持不同输入类型
// 重载签名声明
function getData(input: string): string[];
function getData(input: number): number[];
function getData(input: boolean): { success: boolean };
// 实现函数 - 统一处理逻辑
function getData(input: unknown): unknown {
if (typeof input === "string") {
const result = input.split("");
console.log(`字符串 "${input}" 分割为:`, result);
return result;
} else if (typeof input === "number") {
const result = Array.from({ length: input }, (_, i) => i + 1);
console.log(`生成 ${input} 个数字:`, result);
return result;
} else if (typeof input === "boolean") {
const result = { success: input };
console.log(`布尔值处理结果:`, result);
return result;
}
throw new Error("不支持的输入类型");
}
// 使用示例 - TypeScript会根据参数类型选择正确的重载
const chars = getData("hello"); // string[] 类型
// "字符串 "hello" 分割为:" ["h", "e", "l", "l", "o"]
console.log(chars); // ["h", "e", "l", "l", "o"]
const numbers = getData(5); // number[] 类型
// "生成 5 个数字:" [1, 2, 3, 4, 5]
console.log(numbers); // [1, 2, 3, 4, 5]
const status = getData(true); // { success: boolean } 类型
// "布尔值处理结果:" { success: true }
console.log(status); // { success: true }
// 错误示例:不符合重载签名
// getData([1, 2, 3]); // 错误:没有匹配的重载
🔄 复杂重载:处理多参数场景
// 格式化函数 - 支持多种格式化方式
function format(template: string, ...args: any[]): string;
function format(date: Date): string;
function format(date: Date, pattern: string): string;
function format(input: string | Date, ...args: any[]): string {
if (input instanceof Date) {
const pattern = args[0] || 'YYYY-MM-DD';
if (pattern === 'YYYY-MM-DD') {
const result = input.toISOString().split('T')[0];
console.log(`日期格式化:${result}`);
return result;
} else {
const result = input.toLocaleString();
console.log(`日期本地化:${result}`);
return result;
}
} else {
let result = input;
args.forEach((arg, index) => {
result = result.replace(`{${index}}`, String(arg));
});
console.log(`模板格式化:${result}`);
return result;
}
}
// 使用不同的重载
console.log(format("Hello {0}, you have {1} messages", "Alice", 5));
// "模板格式化:Hello Alice, you have 5 messages"
// 返回:"Hello Alice, you have 5 messages"
console.log(format(new Date()));
// "日期格式化:2024-01-01"
// 返回:"2024-01-01"
console.log(format(new Date(), "locale"));
// "日期本地化:2024/1/1 12:00:00"
// 返回:"2024/1/1 12:00:00"
🔍 重载解析规则:TypeScript会从上到下匹配重载签名,所以更具体的签名应该放在前面
6.5 泛型基础——代码的"万能模具"
泛型就像一个神奇的模具工厂,能够根据需要生产出适合不同材料的精确模具。
🏭 基础泛型:万能工具的诞生
// 1. 身份函数 - 泛型的"Hello World"
function identity<T>(arg: T): T {
console.log(`接收到类型为 ${typeof arg} 的值:`, arg);
return arg;
}
// 使用示例 - 显式指定类型
let output1 = identity<string>("TypeScript");
// "接收到类型为 string 的值:" "TypeScript"
console.log(output1); // "TypeScript"
// 使用示例 - 自动类型推断
let output2 = identity(42);
// "接收到类型为 number 的值:" 42
console.log(output2); // 42
let output3 = identity({ name: "Alice", age: 30 });
// "接收到类型为 object 的值:" { name: "Alice", age: 30 }
console.log(output3); // { name: "Alice", age: 30 }
// 2. 配对函数 - 多类型参数
function pair<T, U>(first: T, second: U): [T, U] {
const result: [T, U] = [first, second];
console.log(`创建配对:[${typeof first}, ${typeof second}]`, result);
return result;
}
const result1 = pair("Alice", 30);
// "创建配对:[string, number]" ["Alice", 30]
console.log(result1); // ["Alice", 30]
const result2 = pair(true, "success");
// "创建配对:[boolean, string]" [true, "success"]
console.log(result2); // [true, "success"]
// 3. 数组反转函数 - 泛型与数组
function reverse<T>(items: T[]): T[] {
const result = [...items].reverse();
console.log(`反转数组:`, items, " -> ", result);
return result;
}
const numbers = [1, 2, 3, 4, 5];
const letters = ["a", "b", "c"];
console.log(reverse(numbers));
// "反转数组:" [1, 2, 3, 4, 5] " -> " [5, 4, 3, 2, 1]
// 返回:[5, 4, 3, 2, 1]
console.log(reverse(letters));
// "反转数组:" ["a", "b", "c"] " -> " ["c", "b", "a"]
// 返回:["c", "b", "a"]
💡 泛型的核心优势:
- 类型保持:保持具体类型信息,不丢失类型细节
- 代码复用:一套代码支持多种类型
- 类型安全:避免使用any类型带来的风险
- 智能提示:IDE能提供准确的代码补全
🎨 泛型的设计哲学
- 参数化类型:将类型作为参数传递
- 延迟绑定:在使用时确定具体类型
- 类型推断:让TypeScript自动推导类型
- 约束机制:限制泛型的使用范围
6.6 泛型约束——给泛型戴上"紧箍咒"
泛型约束就像给万能工具添加安全限制器,确保它只在合适的材料上工作。
🔒 基础约束:限制类型范围
// 1. 长度约束 - 确保对象有length属性
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(arg: T): T {
console.log(`对象长度:${arg.length},类型:${typeof arg}`);
console.log(`内容:`, arg);
return arg;
}
// 可以使用的类型
logLength("hello");
// "对象长度:5,类型:string"
// "内容:" "hello"
logLength([1, 2, 3]);
// "对象长度:3,类型:object"
// "内容:" [1, 2, 3]
logLength({ length: 10, name: "test" });
// "对象长度:10,类型:object"
// "内容:" { length: 10, name: "test" }
// 错误示例:不满足约束
// logLength(42); // 错误!数字没有length属性
// logLength(true); // 错误!布尔值没有length属性
// 2. 对象约束 - 确保参数是对象类型
function mergeObjects<T extends object, U extends object>(obj1: T, obj2: U): T & U {
const result = { ...obj1, ...obj2 };
console.log(`合并对象:`, obj1, " + ", obj2, " = ", result);
return result;
}
const user = { name: "Alice", age: 25 };
const permissions = { level: "admin", canEdit: true };
const merged = mergeObjects(user, permissions);
// "合并对象:" { name: "Alice", age: 25 } " + " { level: "admin", canEdit: true } " = " { name: "Alice", age: 25, level: "admin", canEdit: true }
console.log(merged.name); // "Alice"
console.log(merged.level); // "admin"
console.log(merged.canEdit); // true
// 错误示例:不是对象类型
// mergeObjects("hello", "world"); // 错误!字符串不是对象
// mergeObjects(123, 456); // 错误!数字不是对象
🔑 键约束:安全的属性访问
// keyof约束 - 确保键存在于对象中
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
const value = obj[key];
console.log(`获取属性 ${String(key)}:`, value);
return value;
}
const person = {
name: "Bob",
age: 30,
city: "北京",
isActive: true
};
// 安全的属性访问
console.log(getProperty(person, "name"));
// "获取属性 name:" "Bob"
// 返回:"Bob"
console.log(getProperty(person, "age"));
// "获取属性 age:" 30
// 返回:30
console.log(getProperty(person, "isActive"));
// "获取属性 isActive:" true
// 返回:true
// 错误示例:属性不存在
// getProperty(person, "email"); // 错误!"email"不是person的属性
// getProperty(person, "salary"); // 错误!"salary"不是person的属性
// 多重约束示例
function updateProperty<T extends object, K extends keyof T>(
obj: T,
key: K,
value: T[K]
): T {
const oldValue = obj[key];
obj[key] = value;
console.log(`更新属性 ${String(key)}:${oldValue} -> ${value}`);
return obj;
}
const product = { name: "笔记本", price: 5000, inStock: true };
updateProperty(product, "price", 4500);
// "更新属性 price:5000 -> 4500"
console.log(product.price); // 4500
🎯 条件约束:复杂类型限制
// 条件类型约束
type Stringifiable = string | number | boolean | null | undefined;
function stringify<T extends Stringifiable>(value: T): string {
const result = String(value);
console.log(`字符串化:${typeof value} ${value} -> "${result}"`);
return result;
}
console.log(stringify(42));
// "字符串化:number 42 -> "42""
// 返回:"42"
console.log(stringify(true));
// "字符串化:boolean true -> "true""
// 返回:"true"
console.log(stringify(null));
// "字符串化:object null -> "null""
// 返回:"null"
// 错误示例:不满足条件约束
// stringify({}); // 错误!对象不能字符串化
// stringify([]); // 错误!数组不满足约束
6.7 泛型接口与泛型类——泛型的"高级形态"
泛型接口和泛型类就像高级的工具模板,能够创建出功能强大且类型安全的组件。
📋 泛型接口:定义灵活的契约
// 1. API响应接口 - 通用数据包装器
interface ApiResponse<T> {
code: number;
message: string;
data: T;
timestamp: number;
}
// 创建不同类型的响应
const userResponse: ApiResponse<{ id: number; name: string; email: string }> = {
code: 200,
message: "获取用户成功",
data: { id: 1, name: "Alice", email: "alice@example.com" },
timestamp: Date.now()
};
const productResponse: ApiResponse<string[]> = {
code: 200,
message: "获取产品列表成功",
data: ["手机", "电脑", "平板", "耳机"],
timestamp: Date.now()
};
const errorResponse: ApiResponse<null> = {
code: 404,
message: "资源未找到",
data: null,
timestamp: Date.now()
};
console.log("用户响应:", userResponse);
// "用户响应:" { code: 200, message: "获取用户成功", data: { id: 1, name: "Alice", email: "alice@example.com" }, timestamp: 1704067200000 }
console.log("产品数量:", productResponse.data.length);
// "产品数量:" 4
// 2. 转换器接口 - 函数类型的泛型
interface Transformer<T, U> {
(input: T): U;
description?: string;
}
// 实现不同的转换器
const toUppercase: Transformer<string, string> = (input) => {
const result = input.toUpperCase();
console.log(`大写转换:"${input}" -> "${result}"`);
return result;
};
toUppercase.description = "字符串大写转换器";
const stringToLength: Transformer<string, number> = (input) => {
const result = input.length;
console.log(`长度计算:"${input}" -> ${result}`);
return result;
};
stringToLength.description = "字符串长度计算器";
const numberToBoolean: Transformer<number, boolean> = (input) => {
const result = input > 0;
console.log(`布尔转换:${input} -> ${result}`);
return result;
};
numberToBoolean.description = "数字布尔转换器";
// 使用转换器
console.log(toUppercase("hello world"));
// "大写转换:"hello world" -> "HELLO WORLD""
// 返回:"HELLO WORLD"
console.log(stringToLength("TypeScript"));
// "长度计算:"TypeScript" -> 10"
// 返回:10
console.log(numberToBoolean(-5));
// "布尔转换:-5 -> false"
// 返回:false
🏗️ 泛型类:可复用的组件工厂
// 1. 通用容器类 - 类型安全的数据容器
class Container<T> {
private items: T[] = [];
constructor(initialItems: T[] = []) {
this.items = [...initialItems];
console.log(`创建容器,初始项目数:${this.items.length}`);
}
add(item: T): void {
this.items.push(item);
console.log(`添加项目:`, item, `,当前总数:${this.items.length}`);
}
remove(index: number): T | undefined {
if (index >= 0 && index < this.items.length) {
const removed = this.items.splice(index, 1)[0];
console.log(`移除索引 ${index} 的项目:`, removed);
return removed;
}
console.log(`无效索引:${index}`);
return undefined;
}
get(index: number): T | undefined {
const item = this.items[index];
console.log(`获取索引 ${index} 的项目:`, item);
return item;
}
getAll(): T[] {
console.log(`获取所有项目,共 ${this.items.length} 个`);
return [...this.items];
}
size(): number {
return this.items.length;
}
isEmpty(): boolean {
return this.items.length === 0;
}
}
// 使用不同类型的容器
const numberContainer = new Container<number>([1, 2, 3]);
// "创建容器,初始项目数:3"
numberContainer.add(4);
// "添加项目:" 4 ",当前总数:4"
console.log(numberContainer.get(0));
// "获取索引 0 的项目:" 1
// 返回:1
const stringContainer = new Container<string>();
// "创建容器,初始项目数:0"
stringContainer.add("Hello");
// "添加项目:" "Hello" ",当前总数:1"
stringContainer.add("World");
// "添加项目:" "World" ",当前总数:2"
console.log(stringContainer.getAll());
// "获取所有项目,共 2 个"
// 返回:["Hello", "World"]
// 错误示例:类型不匹配
// numberContainer.add("string"); // 错误!不能将字符串添加到数字容器
// 2. 键值对存储类 - 泛型映射容器
class KeyValueStore<K, V> {
private store = new Map<K, V>();
set(key: K, value: V): void {
const isUpdate = this.store.has(key);
this.store.set(key, value);
console.log(`${isUpdate ? '更新' : '设置'} [${key}] = `, value);
}
get(key: K): V | undefined {
const value = this.store.get(key);
console.log(`获取 [${key}] = `, value);
return value;
}
has(key: K): boolean {
const exists = this.store.has(key);
console.log(`检查键 [${key}] 存在:${exists}`);
return exists;
}
delete(key: K): boolean {
const deleted = this.store.delete(key);
console.log(`删除键 [${key}] ${deleted ? '成功' : '失败'}`);
return deleted;
}
keys(): K[] {
const keyArray = Array.from(this.store.keys());
console.log(`所有键:`, keyArray);
return keyArray;
}
values(): V[] {
const valueArray = Array.from(this.store.values());
console.log(`所有值:`, valueArray);
return valueArray;
}
}
// 使用键值对存储
const userStore = new KeyValueStore<string, { name: string; age: number }>();
userStore.set("user1", { name: "Alice", age: 25 });
// "设置 [user1] = " { name: "Alice", age: 25 }
userStore.set("user2", { name: "Bob", age: 30 });
// "设置 [user2] = " { name: "Bob", age: 30 }
console.log(userStore.get("user1"));
// "获取 [user1] = " { name: "Alice", age: 25 }
// 返回:{ name: "Alice", age: 25 }
console.log(userStore.has("user3"));
// "检查键 [user3] 存在:false"
// 返回:false
🧩 泛型实战:智能数据处理管道
让我们构建一个强大的数据转换管道系统,展示泛型的实际应用价值。
// 定义转换函数类型
type TransformFunc<T, U> = (input: T) => U;
// 智能转换管道类
class DataPipeline<T> {
private transforms: TransformFunc<any, any>[] = [];
private stepNames: string[] = [];
constructor(private initialData: T) {
console.log(`创建数据管道,初始数据:`, initialData);
}
// 添加转换步骤
addStep<U>(transform: TransformFunc<T, U>, stepName: string = '未命名步骤'): DataPipeline<U> {
this.transforms.push(transform);
this.stepNames.push(stepName);
console.log(`添加转换步骤:${stepName}`);
return this as unknown as DataPipeline<U>;
}
// 执行管道转换
execute(): any {
console.log(`\n=== 开始执行数据管道 ===`);
let result = this.initialData;
this.transforms.forEach((transform, index) => {
const stepName = this.stepNames[index];
const oldResult = result;
result = transform(result);
console.log(`步骤 ${index + 1} [${stepName}]:`, oldResult, " -> ", result);
});
console.log(`=== 管道执行完成 ===\n`);
return result;
}
// 获取步骤信息
getSteps(): string[] {
return [...this.stepNames];
}
}
// 示例1:数字处理管道
const numberPipeline = new DataPipeline(10)
.addStep(x => x * 2, "乘以2")
.addStep(x => x + 5, "加5")
.addStep(x => `结果: ${x}`, "格式化为字符串")
.addStep(str => str.toUpperCase(), "转大写");
const result1 = numberPipeline.execute();
// "创建数据管道,初始数据:" 10
// "添加转换步骤:乘以2"
// "添加转换步骤:加5"
// "添加转换步骤:格式化为字符串"
// "添加转换步骤:转大写"
// "=== 开始执行数据管道 ==="
// "步骤 1 [乘以2]:" 10 " -> " 20
// "步骤 2 [加5]:" 20 " -> " 25
// "步骤 3 [格式化为字符串]:" 25 " -> " "结果: 25"
// "步骤 4 [转大写]:" "结果: 25" " -> " "结果: 25"
// "=== 管道执行完成 ==="
console.log("最终结果:", result1); // "最终结果:" "结果: 25"
// 示例2:用户数据处理管道
interface User {
name: string;
age: number;
email: string;
}
const userData: User = {
name: "alice smith",
age: 25,
email: "ALICE@EXAMPLE.COM"
};
const userPipeline = new DataPipeline(userData)
.addStep(user => ({
...user,
name: user.name.split(' ').map(n =>
n.charAt(0).toUpperCase() + n.slice(1).toLowerCase()
).join(' ')
}), "格式化姓名")
.addStep(user => ({
...user,
email: user.email.toLowerCase()
}), "规范化邮箱")
.addStep(user => ({
...user,
isAdult: user.age >= 18
}), "添加成年标识")
.addStep(user => `${user.name} (${user.age}岁) - ${user.email}`, "生成用户摘要");
const result2 = userPipeline.execute();
// "创建数据管道,初始数据:" { name: "alice smith", age: 25, email: "ALICE@EXAMPLE.COM" }
// "添加转换步骤:格式化姓名"
// "添加转换步骤:规范化邮箱"
// "添加转换步骤:添加成年标识"
// "添加转换步骤:生成用户摘要"
// "=== 开始执行数据管道 ==="
// "步骤 1 [格式化姓名]:" { name: "alice smith", age: 25, email: "ALICE@EXAMPLE.COM" } " -> " { name: "Alice Smith", age: 25, email: "ALICE@EXAMPLE.COM" }
// "步骤 2 [规范化邮箱]:" { name: "Alice Smith", age: 25, email: "ALICE@EXAMPLE.COM" } " -> " { name: "Alice Smith", age: 25, email: "alice@example.com" }
// "步骤 3 [添加成年标识]:" { name: "Alice Smith", age: 25, email: "alice@example.com" } " -> " { name: "Alice Smith", age: 25, email: "alice@example.com", isAdult: true }
// "步骤 4 [生成用户摘要]:" { name: "Alice Smith", age: 25, email: "alice@example.com", isAdult: true } " -> " "Alice Smith (25岁) - alice@example.com"
// "=== 管道执行完成 ==="
console.log("用户摘要:", result2); // "用户摘要:" "Alice Smith (25岁) - alice@example.com"
// 示例3:数组处理管道
const arrayPipeline = new DataPipeline([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
.addStep(arr => arr.filter(n => n % 2 === 0), "筛选偶数")
.addStep(arr => arr.map(n => n * n), "计算平方")
.addStep(arr => arr.reduce((sum, n) => sum + n, 0), "求和")
.addStep(sum => ({ total: sum, average: sum / 5 }), "计算统计信息");
const result3 = arrayPipeline.execute();
// "创建数据管道,初始数据:" [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// "添加转换步骤:筛选偶数"
// "添加转换步骤:计算平方"
// "添加转换步骤:求和"
// "添加转换步骤:计算统计信息"
// "=== 开始执行数据管道 ==="
// "步骤 1 [筛选偶数]:" [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] " -> " [2, 4, 6, 8, 10]
// "步骤 2 [计算平方]:" [2, 4, 6, 8, 10] " -> " [4, 16, 36, 64, 100]
// "步骤 3 [求和]:" [4, 16, 36, 64, 100] " -> " 220
// "步骤 4 [计算统计信息]:" 220 " -> " { total: 220, average: 44 }
// "=== 管道执行完成 ==="
console.log("统计结果:", result3); // "统计结果:" { total: 220, average: 44 }
📚 泛型最佳实践指南
🎯 命名规范与代码风格
// ✅ 推荐:使用有意义的类型参数名
function processUserData<UserType, ResultType>(
user: UserType,
processor: (user: UserType) => ResultType
): ResultType {
console.log(`处理用户数据,类型:${typeof user}`);
return processor(user);
}
// ❌ 不推荐:使用无意义的单字母
function processData<T, U, V>(a: T, b: U, c: V) {
// 难以理解各参数的用途
}
// ✅ 推荐:优先使用类型推断
const result = processUserData({ name: "Alice" }, user => user.name.toUpperCase());
// "处理用户数据,类型:object"
console.log(result); // "ALICE"
// ❌ 不推荐:冗余的类型声明
const result2 = processUserData<{name: string}, string>({ name: "Bob" }, user => user.name);
⚖️ 约束使用原则
// ✅ 推荐:适当使用约束,确保类型安全
function processItems<T extends { id: number; name: string }>(items: T[]): string[] {
return items.map(item => {
console.log(`处理项目 ${item.id}: ${item.name}`);
return `[${item.id}] ${item.name}`;
});
}
const products = [
{ id: 1, name: "笔记本", price: 5000 },
{ id: 2, name: "鼠标", price: 100 }
];
const formatted = processItems(products);
// "处理项目 1: 笔记本"
// "处理项目 2: 鼠标"
console.log(formatted); // ["[1] 笔记本", "[2] 鼠标"]
// ❌ 避免:过度约束
function ovlyConstrained<T extends string & number>(value: T) {
// string & number 是不可能的类型
}
🚫 避免过度泛型化
// ✅ 推荐:简单场景直接使用具体类型
function addNumbers(a: number, b: number): number {
const result = a + b;
console.log(`${a} + ${b} = ${result}`);
return result;
}
// ❌ 不推荐:不必要的泛型
function addGeneric<T extends number>(a: T, b: T): T {
return (a + b) as T; // 类型断言,失去了类型安全
}
// ✅ 推荐:真正需要泛型的场景
function combineArrays<T>(arr1: T[], arr2: T[]): T[] {
const result = [...arr1, ...arr2];
console.log(`合并数组:${arr1.length} + ${arr2.length} = ${result.length} 项`);
return result;
}
const numbers = combineArrays([1, 2], [3, 4]);
// "合并数组:2 + 2 = 4 项"
console.log(numbers); // [1, 2, 3, 4]
const strings = combineArrays(["a", "b"], ["c", "d"]);
// "合并数组:2 + 2 = 4 项"
console.log(strings); // ["a", "b", "c", "d"]
🎯 函数与泛型的应用场景
📊 场景对比表
| 特性 | 适用场景 | 示例 |
|---|---|---|
| 基础函数 | 简单的数据处理 | 数学计算、字符串操作 |
| 函数重载 | 多种输入类型的统一处理 | API接口、数据格式化 |
| 泛型函数 | 类型无关的通用逻辑 | 数组操作、对象转换 |
| 泛型约束 | 需要特定能力的类型处理 | 属性访问、接口实现 |
| 泛型类 | 可复用的数据结构和算法 | 容器类、缓存系统 |
| 泛型接口 | 定义灵活的契约和规范 | API响应、事件处理 |
🏗️ 实际项目应用建议
- API层设计:使用泛型接口定义统一的响应格式
- 数据处理:利用泛型函数创建可复用的转换工具
- 状态管理:通过泛型类构建类型安全的状态容器
- 工具函数:结合函数重载处理多种数据类型
- 组件开发:使用泛型约束确保组件的正确使用
总结:构建强大的类型安全工具箱
通过本章的学习,你已经掌握了TypeScript函数与泛型的核心技能:
- ✨ 函数类型定义:为代码添加精确的类型规格说明
- 🎛️ 灵活参数处理:使用可选参数、默认参数和剩余参数
- 🎭 函数重载技术:让一个函数适应多种调用方式
- 🧰 泛型基础应用:创建可复用的类型安全组件
- 🔗 泛型约束机制:在灵活性和安全性之间找到平衡
- 🏗️ 高级泛型结构:构建复杂的泛型接口和泛型类
- 🧩 实战项目应用:将理论知识转化为实际开发能力
🌟 核心价值:函数与泛型是TypeScript最强大的特性组合,它们让你能够构建既灵活又安全的代码基础设施。掌握这些技能,你就拥有了创建高质量、可维护代码的核心能力。
函数与泛型共同构成了类型安全且高度可复用的代码基础。下一章我们将探索高级类型,进一步释放TypeScript类型系统的无限威力!🚀