在 TypeScript 中,泛型和类型工具可以帮助开发者创建灵活而强大的类型定义。下面这行代码展示了 TypeScript 中的一种高级用法:

type UniqueLeftKeys<T, U> = Exclude<UnionKeys<T, U>, keyof U>;

这一行代码定义了一个类型 UniqueLeftKeys,用于从联合键中排除属于第二个泛型类型 U 的键。以下将通过逐个 token 的解析、逻辑推理、以及示例代码,全面阐释其功能与用途。

逐个 token 的解析

type

type 是 TypeScript 的关键字,用于声明一个类型别名。它允许为复杂类型定义更具描述性的名称,从而提高代码的可读性和可维护性。

UniqueLeftKeys<T, U>

这是一个类型别名的声明,其中 UniqueLeftKeys 是类型名称,<T, U> 是类型参数。这里的 TU 表示两个泛型参数,分别可以是任意类型。

  • T:代表第一个输入类型。
  • U:代表第二个输入类型。

=

等号 = 用于将右侧的类型表达式赋值给 UniqueLeftKeys

Exclude<UnionKeys<T, U>, keyof U>

这一部分是类型表达式,使用 TypeScript 提供的工具类型 Exclude 和自定义类型 UnionKeys<T, U>,将 TU 之间的差异提取出来。

UnionKeys<T, U>

  • UnionKeys 是自定义的类型,它的作用是获取 TU 中所有键的联合。

假设 UnionKeys<T, U> 定义如下:

type UnionKeys<T, U> = keyof T | keyof U;

keyof Tkeyof U 分别表示类型 TU 的键名,通过 | 操作符求出它们的并集。

Exclude<A, B>

  • Exclude 是 TypeScript 提供的内置工具类型,用于从类型 A 中排除那些也存在于类型 B 中的部分。

例如:

type Result = Exclude<"a" | "b" | "c", "b" | "c">; // "a"

Result 的值是字符串字面量类型 "a",因为它在第一个集合中,但不在第二个集合中。

keyof U

keyof 是 TypeScript 的关键字,用于提取类型的键名。

例如:

type Example = { a: number; b: string };
type Keys = keyof Example; // "a" | "b"

keyof Example 提取出了 Example 类型中的键,结果是联合类型 "a" | "b"

综上,UniqueLeftKeys<T, U> 表示从 TU 的所有键中排除那些属于 U 的键。

示例代码与运行结果

为了验证上述解析,我们可以构造一个实际例子。

定义辅助类型

以下是完整的辅助类型定义:

type UnionKeys<T, U> = keyof T | keyof U;
type UniqueLeftKeys<T, U> = Exclude<UnionKeys<T, U>, keyof U>;

示例 1:简单对象类型

type ObjectA = { a: number; b: string; c: boolean };
type ObjectB = { b: string; d: number };

type Result = UniqueLeftKeys<ObjectA, ObjectB>;

解析

  • keyof ObjectA"a" | "b" | "c"
  • keyof ObjectB"b" | "d"
  • UnionKeys<ObjectA, ObjectB>"a" | "b" | "c" | "d"
  • Exclude<UnionKeys<ObjectA, ObjectB>, keyof ObjectB> 移除了 "b""d",剩下 "a" | "c"

因此,Result 的值是 "a" | "c"

示例 2:带索引签名的类型

type IndexedA = { [key: string]: number; id: string };
type IndexedB = { name: string; id: string };

type IndexedResult = UniqueLeftKeys<IndexedA, IndexedB>;

解析

  • keyof IndexedAstring | "id"
  • keyof IndexedB"name" | "id"
  • UnionKeys<IndexedA, IndexedB>string | "id" | "name"
  • Exclude<UnionKeys<IndexedA, IndexedB>, keyof IndexedB> 移除了 "name""id",但因为 string 是超集,最终结果是 never

示例 3:泛型参数

function getUniqueKeys<T, U>(obj: T, ref: U): UniqueLeftKeys<T, U>[] {
  const result: (keyof T)[] = Object.keys(obj).filter(
    (key) => !(key in ref)
  ) as (keyof T)[];
  return result as UniqueLeftKeys<T, U>[];
}

const objA = { a: 1, b: 2, c: 3 };
const objB = { b: 2, d: 4 };

const uniqueKeys = getUniqueKeys(objA, objB);
console.log(uniqueKeys); // ["a", "c"]

上述代码通过运行时检查验证了类型定义的正确性。

应用场景

  1. 类型差异检测:比较两个对象类型,找出一个类型中特有的键。
  2. 动态对象过滤:根据参考对象动态筛选出符合条件的键。
  3. 类型辅助工具:用于创建其他高级类型工具。

小结

通过对 UniqueLeftKeys<T, U> 的逐步解析,我们发现它是一种简洁而强大的类型工具,可以广泛应用于对象类型的差异分析中。借助 TypeScript 提供的内置工具类型和泛型功能,这种类型表达式在复杂场景下提供了显著的便利。