mcpField
U
unify-types

unify-types

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

关于 unify-types

面向 TypeScript 开发者的编码技能,帮助判断相似类型应合并还是分开。它权衡简化处理与保留重要类型安全的需求。

主要功能

  1. 01合并相似类型的指导
  2. 02保留独立类型的判断标准
  3. 03标记联合类型建议
  4. 04检查重复的类型收窄

适用场景

  1. 01检查差异较小的并行接口
  2. 02简化属性相似的联合类型
  3. 03评估类型差异对类型安全是否重要

安装

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

$npx skills add https://github.com/marius-townhouse/effective-typescript-skills --skill unify-types

安装后在 Claude Code 中输入/unify-types确认已加载。

详细介绍

优先统一类型,而不是对差异建模

概述

细微差异通常不值得单独定义类型。

当类型几乎相同时,统一它们可以简化代码。处理细微差异的成本通常低于维护并行的类型层次结构。

何时使用此方法

  • 仅有一两个属性不同的类型
  • 大部分相同的联合类型
  • 正在考虑为不同变体定义单独的类型
  • 简化复杂的类型关系

铁律

Unify types unless differences are fundamental.
Small variations can be handled at runtime.

请记住:

  • 重复的类型 = 重复的处理代码
  • 相似类型的联合 = 反复进行类型收窄
  • 一个灵活的类型通常比多个特定类型更简单
  • 不妨问问:这些差异真的对类型系统重要吗?

检测:并行类型

// Two types that are almost identical
interface Dog {
  name: string;
  breed: string;
  barkVolume: number;
}

interface Cat {
  name: string;
  breed: string;
  meowVolume: number;
}

type Pet = Dog | Cat;

// Every function needs to narrow:
function getPetInfo(pet: Pet): string {
  if ('barkVolume' in pet) {
    return `${pet.name} barks at ${pet.barkVolume}`;
  } else {
    return `${pet.name} meows at ${pet.meowVolume}`;
  }
}

统一方案

interface Pet {
  name: string;
  breed: string;
  vocalizationType: 'bark' | 'meow';
  vocalizationVolume: number;
}

function getPetInfo(pet: Pet): string {
  return `${pet.name} ${pet.vocalizationType}s at ${pet.vocalizationVolume}`;
}

无需进行类型收窄。一个类型就能处理两种情况。

何时保留单独的类型

行为存在根本差异
// These really are different
interface File {
  path: string;
  read(): Buffer;
  write(data: Buffer): void;
}

interface Directory {
  path: string;
  list(): string[];
  create(name: string): void;
}

文件和目录的操作不同。将它们统一会失去类型安全性。

基数不同
// Different structures
interface SingleResult {
  value: number;
}

interface MultipleResults {
  values: number[];
  average: number;
}

它们的形状确实不同。

实用示例:API 响应

// Separate types
interface SuccessResponse {
  status: 'success';
  data: Data;
  timestamp: Date;
}

interface ErrorResponse {
  status: 'error';
  error: string;
  timestamp: Date;
}

type Response = SuccessResponse | ErrorResponse;

// Handling requires narrowing everywhere
function logResponse(res: Response) {
  console.log(`${res.timestamp}: ${res.status}`);
  if (res.status === 'success') {
    console.log(res.data);  // Need to narrow
  }
}

考虑一下统一后是否更简单:

// Unified type
interface Response {
  status: 'success' | 'error';
  data?: Data;      // Present on success
  error?: string;   // Present on error
  timestamp: Date;
}

// Can access common fields without narrowing
function logResponse(res: Response) {
  console.log(`${res.timestamp}: ${res.status}`);
  if (res.data) {
    console.log(res.data);
  }
}

折中方案:带标签的联合类型

有时,带标签的联合类型是恰当的折中方案:

// Tagged union: explicit about differences, unified handling
interface Animal {
  name: string;
  breed: string;
}

interface Dog extends Animal {
  type: 'dog';
  barkVolume: number;
}

interface Cat extends Animal {
  type: 'cat';
  meowVolume: number;
}

type Pet = Dog | Cat;

// Common operations don't need narrowing
function getName(pet: Pet): string {
  return pet.name;  // Works for both
}

// Type-specific operations are explicit
function getVolume(pet: Pet): number {
  return pet.type === 'dog' ? pet.barkVolume : pet.meowVolume;
}

成本效益分析

创建单独的类型之前,先问问自己:

  1. 我有多常需要特定类型的行为?

    • 很少 → 统一
    • 经常 → 分开
  2. 差异是结构性的还是语义上的?

    • 结构相同、含义不同 → 可以考虑统一
    • 结构不同 → 保持分开
  3. 统一会失去重要的类型安全性吗?

    • 会 → 保持分开
    • 不会 → 统一

实例:事件

// Over-differentiated
interface ClickEvent {
  type: 'click';
  x: number;
  y: number;
  target: Element;
}

interface KeyEvent {
  type: 'key';
  key: string;
  target: Element;
}

interface ScrollEvent {
  type: 'scroll';
  scrollTop: number;
  target: Element;
}

可以统一共有部分:

interface BaseEvent {
  type: string;
  target: Element;
}

interface ClickEvent extends BaseEvent {
  type: 'click';
  x: number;
  y: number;
}

// etc.

如果差异很少影响实际使用,也可以完全统一:

interface UIEvent {
  type: 'click' | 'key' | 'scroll';
  target: Element;
  details: ClickDetails | KeyDetails | ScrollDetails;
}

抵制压力的原则

1. “类型应该精确”

压力:“单独的类型更准确”

**回应:**精确性会增加代码复杂度。

**行动:**权衡精确性的好处与处理复杂度。

2. “它们以后可能会分化”

压力:“先分开,以便将来灵活调整”

**回应:**这就是 YAGNI。如果它们真的分化,再重构。

**行动:**现在先统一;有需要时再分开。

危险信号——停下来重新考虑

  • 大多数属性都相同的联合类型
  • 为相似类型反复编写类型收窄代码
  • 仅有一个属性名称不同的类型
  • 为“不同”类型编写并行实现

常见借口(均不成立)

借口事实
“它们在概念上不同”代码不关心概念
“单独的类型更简洁”类型越多,处理代码就越多
“我们可能需要区分它们”真遇到问题时再处理

快速参考

// DON'T: Separate types for minor differences
interface Dog { name: string; bark: () => void; }
interface Cat { name: string; meow: () => void; }
type Pet = Dog | Cat;

// DO: Unified type
interface Pet {
  name: string;
  sound: 'bark' | 'meow';
  makeSound: () => void;
}

// DO: Keep separate when genuinely different
interface File { read(): Buffer; write(b: Buffer): void; }
interface Directory { list(): string[]; }

总结

除非差异是根本性的,否则就统一类型。

单独的类型意味着到处都要编写单独的处理代码。当类型的大部分属性相同、只有细节不同,用带可选字段或变体字段的统一类型通常更简单。

参考资料

基于 Dan Vanderkam 的《Effective TypeScript》第 39 条:优先统一类型,而不是对差异建模。

评论

支持 Markdown