TypeScript入门(六)函数与泛型:打造灵活的"类型安全工具"

221 阅读10分钟

第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响应、事件处理

🏗️ 实际项目应用建议

  1. API层设计:使用泛型接口定义统一的响应格式
  2. 数据处理:利用泛型函数创建可复用的转换工具
  3. 状态管理:通过泛型类构建类型安全的状态容器
  4. 工具函数:结合函数重载处理多种数据类型
  5. 组件开发:使用泛型约束确保组件的正确使用

总结:构建强大的类型安全工具箱

通过本章的学习,你已经掌握了TypeScript函数与泛型的核心技能:

  • 函数类型定义:为代码添加精确的类型规格说明
  • 🎛️ 灵活参数处理:使用可选参数、默认参数和剩余参数
  • 🎭 函数重载技术:让一个函数适应多种调用方式
  • 🧰 泛型基础应用:创建可复用的类型安全组件
  • 🔗 泛型约束机制:在灵活性和安全性之间找到平衡
  • 🏗️ 高级泛型结构:构建复杂的泛型接口和泛型类
  • 🧩 实战项目应用:将理论知识转化为实际开发能力

🌟 核心价值:函数与泛型是TypeScript最强大的特性组合,它们让你能够构建既灵活又安全的代码基础设施。掌握这些技能,你就拥有了创建高质量、可维护代码的核心能力。

函数与泛型共同构成了类型安全且高度可复用的代码基础。下一章我们将探索高级类型,进一步释放TypeScript类型系统的无限威力!🚀