TS 类型体操技巧总结

TS 类型体操技巧总结

💡 原文中文,约14700字,阅读约需35分钟。
📝

内容提要

本文总结 TypeScript 类型体操技巧:用元组或函数包装判断 never,反向 extends 判断 unknown 和 number,用函数签名比较判断类型相等;介绍 PropertyKey、Record、Omit 实现合并、映射键过滤、ThisType 混入 data、infer 配合 extends 限定推断;还讲解递归循环、排列组合、Union 转 Intersection 等,并强调解题思路比技巧更重要。

🔎

延伸解读

分配特性:类型体操中的双刃剑

文章多次提到 union 的分配特性,它既是实现排列组合等复杂类型的基础,也是导致 never 判断、类型相等判断出错的根源。理解分配特性何时触发、如何通过元组或函数包装阻止,是避免类型体操陷阱的关键。

递归:类型体操的核心思路

文章强调递归在类型体操中的重要性,并区分了循环和化为子问题两种模式。循环常用辅助泛型参数暂存中间结果,而化为子问题则更贴近通用的问题解决思路。掌握递归模式比记忆具体技巧更有助于应对新题目。

编码习惯:提升类型代码可读性

文章建议使用有意义的泛型参数名、遵循缩进规范、抽离中间类型等。这些习惯不仅能让他人更容易理解,也能在编写长类型时避免混乱。良好的编码习惯是类型体操从炫技走向实用的重要一步。

实战与平衡:类型体操的适用场景

文章通过全排列、组合、Zip 等实战题展示了技巧的综合运用,但也提醒读者类型体操玩归玩,别上头。工作中应权衡时间成本,在紧迫时可用 @ts-expect-error 暂代,但不应滥用 any,平时多花时间提升类型声明质量。

❓

Q&A

TypeScript 中如何正确判断一个类型是否为 never?

可以使用元组或函数包装来阻止 union 分配特性。例如:type IsNever<T> = [T] extends [never] ? true : false; 或 type IsNever<T> = (() => T) extends () => never ? true : false。直接使用 T extends never 会因分配特性返回 never。

TypeScript 中如何判断一个类型是否为 unknown 或 number?

判断 unknown 应使用 unknown extends T ? true : false,因为 unknown 是 top 类型。判断 number 要排除字面量类型,应使用 number extends T ? true : false,这样对于字面量如 2 会返回 false。

TypeScript 中如何准确判断两个类型是否相等?

可以使用函数签名比较:type Equals<A, B> = (<T>() => T extends A ? 0 : 1) extends <T>() => T extends B ? 0 : 1 ? true : false; 双向 extends 方法无法处理 any,而此方法可以。

TypeScript 中如何实现两个对象的合并类型?

可以利用 Omit 和交叉类型:type Merge<A, B> = Omit<Omit<A, keyof B> & B, never>; 其中 Omit<I, never> 可将交叉类型转为接口类型。

TypeScript 中 ThisType 有什么作用?

ThisType 是一个标记类型,用于给方法中的 this 混入其他属性。例如在 Vue 的 options 中,可以用 ThisType 让 computed 和 methods 中的 this 包含 data 和其他方法。

TypeScript 中 infer 如何配合 extends 使用?

在模式匹配时,可以用 extends 限定 infer 推断出的类型。例如:type StringToNumber<S> = S extends `${infer Num extends number}` ? Num : never; 这样能确保推断出的 Num 是 number 类型。

TypeScript 中如何实现 Union 转 Intersection?

可以使用 UnionToIntersection:type UnionToIntersection<U> = (U extends unknown ? (arg: U) => void : never) extends (arg: infer I) => void ? I : never; 利用函数参数的逆变位置推断出交叉类型。

TypeScript 类型体操中递归有哪些常见用法?

递归常用于实现循环和化大为小。循环可通过递归+下标或递归+模式匹配实现;化大为小则是将问题分解为子问题,如全排列、组合等。

TypeScript 中如何实现全排列类型?

可以使用递归:type Permutation<U, E = U> = [U] extends [never] ? [] : E extends U ? [E, ...Permutation<Exclude<U, E>>] : never; 利用 union 分配特性和元组解构。

TypeScript 类型体操有哪些编码习惯建议?

建议使用有意义的泛型参数名,如 S、Num、Strs、First、Rest 等;对 union 映射时用额外参数保存原 union;声明数组时尽量用 readonly;长类型注意缩进、抽离中间类型、增加辅助泛型参数。

🏷️

标签

➡️

继续阅读