mcpField
G
generics-as-functions

generics-as-functions

github.com/marius-townhouse开发643 次安装38 周前更新
代码仓库

关于 generics-as-functions

这项 TypeScript 技能将泛型理解为作用于类型的函数,引导开发者设置约束、选择名称并编写文档,帮助设计可复用的泛型类型与函数。

主要功能

  1. 01将泛型视为类型转换
  2. 02使用 extends 约束参数
  3. 03选择描述性类型参数名称
  4. 04用 @template 记录参数
  5. 05识别有问题的泛型模式

适用场景

  1. 01定义可复用的类型工具
  2. 02设计泛型函数和类
  3. 03阻止无效的泛型实参
  4. 04完善公开泛型的文档

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/marius-townhouse/effective-typescript-skills --skill generics-as-functions

安装后在 Claude Code 中输入/generics-as-functions确认已加载。

详细介绍

将泛型视为类型之间的函数

概述

泛型类型相当于值空间中函数在类型层面的对应形式。正如函数接收参数并返回值,泛型类型接收类型参数并生成具体类型。这个思维模型能帮助你编写更好的泛型类型:运用编写函数时的相同原则,包括约束输入、选择恰当的名称和记录行为。

将泛型理解为类型之间的函数,有助于明确何时使用约束、如何命名类型参数,以及为什么某些泛型模式有效而另一些无效。这种视角对于在 TypeScript 中有效进行类型层面编程至关重要。

何时使用此技能

  • 定义可转换其他类型的泛型类型
  • 编写带有类型参数的泛型函数
  • 限制可传入泛型的类型
  • 使用 TSDoc 记录泛型类型
  • 创建可复用的类型工具

核心规则

将泛型类型视为类型之间的函数:使用 extends 像类型注解一样约束输入,选择描述性名称,并使用 @template TSDoc 记录文档。

检测

留意以下模式:

// RED FLAGS - Poor generic design
type MyPick<T, K> = { [P in K]: T[P] };  // No constraints, errors in implementation
type BadGeneric<X, Y, Z> = ...;  // Single-letter names without context
function parse<T>(input: string): T;  // Return-only generic, no better than any

将泛型类型视为函数

泛型类型接收类型参数并生成具体类型:

// Generic type "function"
type MyPartial<T> = { [K in keyof T]?: T[K] };

// "Calling" the function with Person
type PartPerson = MyPartial<Person>;
// Equivalent to: { name?: string; age?: number; }

与函数一样,泛型类型可以有多个参数:

// Two type parameters
type MyPick<T, K extends keyof T> = { [P in K]: T[P] };

// Usage
type NameOnly = MyPick<Person, 'name'>;
// Equivalent to: { name: string }

约束类型参数

使用 extends 约束类型参数,就像使用类型注解约束函数参数一样:

// GOOD: Constrained type parameters
type MyPick<T extends object, K extends keyof T> = {
  [P in K]: T[P]
};

// Without constraints - allows invalid instantiations
type BadPick<T, K> = { [P in K]: T[P] };  // Errors in implementation

// Invalid uses caught by constraints:
type Bad1 = MyPick<Person, 'firstName'>;  // Error: 'firstName' not in Person
type Bad2 = MyPick<'age', Person>;  // Error: string doesn't satisfy object

命名类型参数

选择描述性名称,尤其是对于复杂的泛型:

// Short names OK for simple, local generics
type Partial<T> = { [K in keyof T]?: T[K] };

// Longer names for complex or exported generics
type MapValues<
  ObjectType extends object,
  ValueTransformer extends (value: any) => any
> = {
  [Key in keyof ObjectType]: ValueTransformer<ObjectType[Key]>
};

记录泛型

使用 @template TSDoc 标签记录类型参数:

/**
 * Construct a new object type using a subset of properties from another.
 * @template T - The original object type
 * @template K - The keys to pick, typically a union of string literal types
 */
type MyPick<T extends object, K extends keyof T> = {
  [P in K]: T[P]
};

泛型函数

泛型函数定义相关的泛型类型,并支持类型推断:

function pick<T extends object, K extends keyof T>(
  obj: T,
  ...keys: K[]
): Pick<T, K> {
  const result: Partial<Pick<T, K>> = {};
  for (const k of keys) {
    result[k] = obj[k];
  }
  return result as Pick<T, K>;
}

// TypeScript infers types from arguments
const person = { name: 'Alice', age: 30 };
const nameOnly = pick(person, 'name');
// Type: Pick<{ name: string; age: number }, 'name'>

泛型类

泛型类用于保存无需传递给方法的类型:

class Box<T> {
  value: T;
  constructor(value: T) {
    this.value = value;
  }
  getValue(): T {
    return this.value;
  }
}

// Type inferred from constructor
const dateBox = new Box(new Date());
// Type: Box<Date>

抗压规则

当有人施压要求使用无限制的泛型时:

  1. 添加约束:使用 extends 限制有效的类型实参
  2. 考虑默认值:为类型参数提供合理的默认值
  3. 记录要求:使用 TSDoc 解释约束
  4. 测试边缘情况:验证泛型适用于联合类型和边缘情况

危险信号

反模式问题所在
不受约束的类型参数允许无效实例化,导致实现错误
复杂泛型中使用单字母名称降低可读性
仅用于返回值的泛型等同于类型断言,不提供类型安全性
公共泛型缺少 TSDoc开发者体验不佳

常见借口

“约束会限制灵活性”

实际情况:约束能在类型层面捕获错误,避免产生令人困惑的类型错误或错误类型。它们还会说明有效用法。

“T、K、V 是标准名称”

实际情况:简单情况下使用这些惯例名称没问题,但在复杂泛型中,描述性名称更易读。名称长度应与作用域相匹配。

“用户能自行弄清楚类型”

实际情况:文档能帮助用户理解泛型,而无需阅读实现。IDE 工具提示中会显示 @template 标签。

快速参考

概念值层面类型层面
定义functiontype
参数(x: T)<T extends Constraint>
返回: ReturnType= ResultType
文档@param@template
约束类型注解extends 关键字

总结

泛型类型是类型之间的函数。运用编写函数时的相同原则:约束输入、选择有意义的名称,并充分记录文档。这个思维模型能让复杂的类型层面代码更易理解和维护。

参考资料

  • Dan Vanderkam 著《Effective TypeScript》第 2 版
  • 条目 50:将泛型视为类型之间的函数

评论

支持 Markdown