在 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> 是类型参数。这里的 T 和 U 表示两个泛型参数,分别可以是任意类型。
T:代表第一个输入类型。U:代表第二个输入类型。
=
等号 = 用于将右侧的类型表达式赋值给 UniqueLeftKeys。
Exclude<UnionKeys<T, U>, keyof U>
这一部分是类型表达式,使用 TypeScript 提供的工具类型 Exclude 和自定义类型 UnionKeys<T, U>,将 T 和 U 之间的差异提取出来。
UnionKeys<T, U>
UnionKeys是自定义的类型,它的作用是获取T和U中所有键的联合。
假设 UnionKeys<T, U> 定义如下:
type UnionKeys<T, U> = keyof T | keyof U;
keyof T 和 keyof U 分别表示类型 T 和 U 的键名,通过 | 操作符求出它们的并集。
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> 表示从 T 和 U 的所有键中排除那些属于 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 IndexedA是string | "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"]
上述代码通过运行时检查验证了类型定义的正确性。
应用场景
- 类型差异检测:比较两个对象类型,找出一个类型中特有的键。
- 动态对象过滤:根据参考对象动态筛选出符合条件的键。
- 类型辅助工具:用于创建其他高级类型工具。
小结
通过对 UniqueLeftKeys<T, U> 的逐步解析,我们发现它是一种简洁而强大的类型工具,可以广泛应用于对象类型的差异分析中。借助 TypeScript 提供的内置工具类型和泛型功能,这种类型表达式在复杂场景下提供了显著的便利。
















