typescirpt 类型自动推断

75 阅读3分钟

我们经常会遇到这种需求,就是一个类型是依托于另外一个类型的。下面以具体的代码作为示例进行讲解

// 简单说来就是,type  为 dog 时是 run 方法,而 type 是 cat 时,则为 eat 方法
const animals: any[] = [{ type: 'dog', methods: { run: ()= {} }, { type: 'cat', methods: { eat: ()=> {}}]

为了解决这个问题,下面有三种方式可供参考

✅ 第一种:直接写联合类型

interface Dog {
  type: "dog";
  methods: { run: () => void };
}

interface Cat {
  type: "cat";
  methods: { eat: () => void };
}
type Union = Dog | Cat;

const _list: Union[] = [{ type: "dog", methods: { run: () => {} } }];

📚 专业术语:

项目专业术语说明
`DogCat`
type: "dog"✅ 判别式联合(Discriminated Union)或标签联合(Tagged Union)

🔍 特点:

  • 适用于类型固定不变的场景;
  • 适合配合 switchif 做类型缩窄(narrowing);
  • 是 TypeScript 类型建模最基础、最推荐的一种方式。

✅ 第二种:基于 key 映射的联合类型分发

// 这里做条件判断
type Methods<T> = T extends "dog" ? { run: () => void } : { eat: () => void };
type TypeKey = "dog" | "cat";
// 这里做自动生成联合类型
type AnimalUnion = {
  [K in TypeKey]: {
    type: K;
    methods: Methods<K>;
  };
}[TypeKey];
const _list2: AnimalUnion[] = [{ type: "cat", methods: { eat: () => {} } }];

重点讲一下AnimalUnion这个写法是什么意思

type AnimalUnion = {
  [K in TypeKey]: {
    type: K;
    methods: Methods<K>;
  };
}[TypeKey];

这是 映射类型 + 索引访问类型 组合使用的高级技巧,它的核心作用是:

根据联合类型 TypeKey = "dog" | "cat",构造出多个具名对象类型,再通过索引 [TypeKey] 把它们变成联合类型


前提定义

type TypeKey = "dog" | "cat";

type Methods<T> = T extends "dog"
  ? { run: () => void }
  : { eat: () => void };

🧩 步骤 1:[K in TypeKey]映射类型

{
  [K in TypeKey]: {
    type: K;
    methods: Methods<K>;
  }
}

这会生成一个类型对象,类似于:

{
  dog: {
    type: "dog";
    methods: { run: () => void };
  };
  cat: {
    type: "cat";
    methods: { eat: () => void };
  };
}

注意:这里的 K 是泛型变量,值为 "dog" 和 "cat" 各走一轮,然后 Methods<K> 自动类型分发。


📦 步骤 2:[...]索引访问类型,把对象类型转为联合类型

{
  dog: {...};
  cat: {...};
}["dog" | "cat"]

就等价于:

{
  type: "dog";
  methods: { run: () => void };
} | {
  type: "cat";
  methods: { eat: () => void };
}

也就是你要的:

type AnimalUnion =
  | { type: "dog"; methods: { run: () => void } }
  | { type: "cat"; methods: { eat: () => void } }

等价于手写的联合类型版本,但具有以下优势:

🧱 图示理解

// 中间类型结构:
{
  dog: { type: "dog"; methods: { run: () => void } };
  cat: { type: "cat"; methods: { eat: () => void } };
}

// 取联合:
{
  ...dog内容
} | {
  ...cat内容
}

✅ 第三种:泛型函数 + 类型映射

// 构造一个映射的 Map
interface AnimalMap {
  dog: { run: () => void };
  cat: { eat: () => void };
}
type AnimalType = keyof AnimalMap;
type AnimalMethods<T extends AnimalType> = AnimalMap[T];
// 设置统一泛型
interface Animal<T extends AnimalType = AnimalType> {
  type: T;
  methods: AnimalMethods<T>;
}
// 泛型函数来精确推导
// 错误写法❌,就当错误案例了,这种直接定义整个数组是不行的,会导致类型宽泛
function defineAnimals<T extends AnimalType>(
  animals: Animal<T>[]
): Animal<T>[] {
  return animals;
}
const _list3: Animal[] = defineAnimals([
  {
    type: "cat",
    methods: {
      eat: () => {}
    }
  },
  {
    type: "dog",
    methods: {
      eat: () => {} // 这里类型就不对了,你写一个没事,但是数组里一旦多出一项就会出问题
    }
  }
]);

// 正确写法✅
// 只针对每一项做泛型函数推导,就不会有问题
function defineAnimal<T extends AnimalType>(animal: Animal<T>): Animal<T> {
  return animal;
}
const _list3: Animal[] = [
  defineAnimal({ type: "cat", methods: { eat: () => {} } }),
  defineAnimal({ type: "dog", methods: { run: () => {} } })
];

📚 专业术语:

项目专业术语说明
Animal<T>✅ 泛型接口(Generic Interface)
AnimalMap[T]✅ 索引访问类型(Indexed Access Types)
defineAnimals✅ 泛型函数(Generic Function)
推导 T✅ 类型参数推导(Type Inference)

🔍 特点:

  • 泛型推导让类型自动推断,更安全;
  • 在库设计或工具函数中极为常见;
  • 更强的类型可推导性(适合函数式设计)。

🔄 三者对比总结表格

实现方式技术核心主要术语优势适用场景
第一种:直接联合类型枚举式联合联合类型、判别式联合简单直观,缩窄友好类型数量少、结构固定
第二种:类型映射构造联合条件分发 + 映射类型条件类型、映射类型、分布式联合动态映射、结构清晰类型与 key 有明显对应
第三种:泛型函数泛型 + 索引类型泛型函数、索引访问类型类型推导强、灵活动态生成、函数设计