前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >TypeScript 条件类型精读与实践

TypeScript 条件类型精读与实践

作者头像
牧云云
发布于 2021-10-09 02:59:40
发布于 2021-10-09 02:59:40
77500
代码可运行
举报
文章被收录于专栏:云瓣云瓣
运行总次数:0
代码可运行

在大多数程序中,我们必须根据输入做出决策。TypeScript 也不例外,使用条件类型可以描述输入类型与输出类型之间的关系。

本文同步首发在个人博客中,欢迎订阅、交流。

用于条件判断时的 extends

当 extends 用于表示条件判断时,可以总结出以下规律

  1. 若位于 extends 两侧的类型相同,则 extends 在语义上可理解为 ===,可以参考如下例子:
代码语言:javascript
代码运行次数:0
运行
复制
type result1 = 'a' extends 'abc' ? true : false // false
type result2 = 123 extends 1 ? true : false     // false
  1. 若位于 extends 右侧的类型包含位于 extends 左侧的类型(即狭窄类型 extends 宽泛类型)时,结果为 true,反之为 false。可以参考如下例子:
代码语言:javascript
代码运行次数:0
运行
复制
type result3 = string extends string | number ? true : false // true
  1. 当 extends 作用于对象时,若在对象中指定的 key 越多,则其类型定义的范围越狭窄。可以参考如下例子:
代码语言:javascript
代码运行次数:0
运行
复制
type result4 = { a: true, b: false } extends { a: true } ? true : false // true

在泛型类型中使用条件类型

考虑如下 Demo 类型定义:

代码语言:javascript
代码运行次数:0
运行
复制
type Demo<T, U> = T extends U ? never : T

结合用于条件判断时的 extends,可知 'a' | 'b' | 'c' extends 'a' 是 false, 因此 Demo<'a' | 'b' | 'c', 'a'> 结果是 'a' | 'b' | 'c' 么?

查阅官网,其中有提到:

When conditional types act on a generic type, they become distributive when given a union type.

即当条件类型作用于泛型类型时,联合类型会被拆分使用。即 Demo<'a' | 'b' | 'c', 'a'> 会被拆分为 'a' extends 'a''b' extends 'a''c' extends 'a'。用伪代码表示类似于:

代码语言:javascript
代码运行次数:0
运行
复制
function Demo(T, U) {
  return T.map(val => {
    if (val !== U) return val
    return 'never'
  })
}

Demo(['a', 'b', 'c'], 'a') // ['never', 'b', 'c']

此外根据 never 类型的定义 —— never 类型可分配给每种类型,但是没有类型可以分配给 never(除了 never 本身)。即 never | 'b' | 'c' 等价于 'b' | 'c'

因此 Demo<'a' | 'b' | 'c', 'a'> 的结果并不是 'a' | 'b' | 'c' 而是 'b' | 'c'

工具类型

心细的读者可能已经发现了 Demo 类型的声明过程其实就是 TypeScript 官方提供的工具类型中 Exclude<Type, ExcludedUnion> 的实现原理,其用于将联合类型 ExcludedUnion 排除在 Type 类型之外。

代码语言:javascript
代码运行次数:0
运行
复制
type T = Demo<'a' | 'b' | 'c', 'a'> // T: 'b' | 'c'

基于 Demo 类型定义,进一步地还可以实现官方工具类型中的 Omit<Type, Keys>,其用于移除对象 Type 中满足 keys 类型的属性值。

代码语言:javascript
代码运行次数:0
运行
复制
type Omit<Type, Keys> = {
  [P in Demo<keyof Type, Keys>]: Type<P>
}

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

type T = Omit<Todo, 'description'> // T: { title: string; completed: boolean }

逃离舱

如果想让 Demo<'a' | 'b' | 'c', 'a'> 的结果为 'a' | 'b' | 'c' 是否可以实现呢? 根据官网描述:

Typically, distributivity is the desired behavior. To avoid that behavior, you can surround each side of the extends keyword with square brackets.

如果不想遍历泛型中的每一个类型,可以用方括号将泛型给括起来以表示使用该泛型的整体部分。

代码语言:javascript
代码运行次数:0
运行
复制
type Demo<T, U> = [T] extends [U] ? never : T

// result 此时类型为 'a' | 'b' | 'c'
type result = Demo<'a' | 'b' | 'c', 'a'>

在箭头函数中使用条件类型

在箭头函数中使用三元表达式时,从左向右的阅读习惯导致函数内容区若不加括号则会让使用方感到困惑。比如下方代码中 x 是函数类型还是布尔类型呢?

代码语言:javascript
代码运行次数:0
运行
复制
// The intent is not clear.
var x = a => 1 ? true : false

在 eslint 规则 no-confusing-arrow 中,推荐如下写法:

代码语言:javascript
代码运行次数:0
运行
复制
var x = a => (1 ? true : false)

在 TypeScript 的类型定义中,若在箭头函数中使用 extends 也是同理,由于从左向右的阅读习惯,也会导致阅读者对类型代码的执行顺序感到困惑。

代码语言:javascript
代码运行次数:0
运行
复制
type Curry<P extends any[], R> =
  (arg: Head<P>) => HasTail<P> extends true ? Curry<Tail<P>, R> : R

因此在箭头函数中使用 extends 建议加上括号,对于进行 code review 有很大的帮助。

代码语言:javascript
代码运行次数:0
运行
复制
type Curry<P extends any[], R> =
  (arg: Head<P>) => (HasTail<P> extends true ? Curry<Tail<P>, R> : R)

结合类型推导使用条件类型

在 TypeScript 中,一般会结合 extends 来使用类型推导 infer 语法。使用它可以实现自动推导类型的目的。比如用其来实现工具类型 ReturnType<Type>,该工具类型用于返回函数 Type 的返回类型。

代码语言:javascript
代码运行次数:0
运行
复制
type ReturnType<T extends Function> = T extends (...args: any) => infer U ? U : never

MyReturnType<() => string>          // string
MyReturnType<() => Promise<boolean> // Promise<boolean>

结合 extends 与类型推导还可以实现与数组相关的 Pop<T>Shift<T>Reverse<T> 工具类型。

Pop<T>:

代码语言:javascript
代码运行次数:0
运行
复制
type Pop<T extends any[]> = T extends [...infer ExceptLast, any] ? ExceptLast : never

type T = Pop<[3, 2, 1]> // T: [3, 2]

Shift<T>:

代码语言:javascript
代码运行次数:0
运行
复制
type Shift<T extends any[]> = T extends [infer _, ...infer O] ? O : never

type T = Shift<[3, 2, 1]> // T: [2, 1]

Reverse<T>

代码语言:javascript
代码运行次数:0
运行
复制
type Reverse<T> = T extends [infer F, ...infer Others]
  ? [...Reverse<Others>, F]
  : []

type T = Reverse<['a', 'b']> // T: ['b', 'a']

使用条件类型来判断两个类型完全相等

我们也可以使用条件类型来判断 A、B 两个类型是否完全相等。当前社区上主要有两种方案:

方案一: 参考 issue

代码语言:javascript
代码运行次数:0
运行
复制
export type Equal1<T, S> =
  [T] extends [S] ? (
    [S] extends [T] ? true : false
  ) : false

目前该方案的唯一缺点是会将 any 类型与其它任何类型判为相等。

代码语言:javascript
代码运行次数:0
运行
复制
type T = Equal1<{x:any}, {x:number}> // T: true

方案二: 参考 issue

代码语言:javascript
代码运行次数:0
运行
复制
export type Equal2<X, Y> =
  (<T>() => T extends X ? 1 : 2) extends
  (<U>() => U extends Y ? 1 : 2) ? true : false

目前该方案的唯一缺点是在对交叉类型的处理上有一点瑕疵。

代码语言:javascript
代码运行次数:0
运行
复制
type T = Equal2<{x:1} & {y:2}, {x:1, y:2}> // false

以上两种判断类型相等的方法见仁见智,笔者在此抛砖引玉。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-10-04 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
TypeScript 体操,从进阶到放弃!
最近有了面试的打算,所以抽空整理了一些高难度的内容,有兴趣的小伙伴可以跟我一起慢慢过一遍,一定要要自己手写一遍,不会了在来这里参考!
萌萌哒草头将军
2025/04/30
1700
TypeScript 体操,从进阶到放弃!
TypeScript的另一面:类型编程
作为前端开发的趋势之一,TypeScript 正在越来越普及,很多人像我一样写了 TS 后再也回不去了,比如写再小的demo也要用 TS(得益于ts-node[1]),JS 只有在配置文件如Webpack(实际上,接下来肯定会有用TS写配置文件的趋势,如Vite)、ESLint等时才会用到。但同样,也有部分开发者对TS持有拒绝的态度,如nodemon的作者就曾表示自己从来没有使用过TS(见 #1565[2])。但同样还有另外一部分人认为TS学习成本太高,所以一直没有开始学习的决心。
zz_jesse
2021/07/30
1.8K0
TypeScript 类型体操 03
发现一个好玩的开源项目:type-challenges,在上面可以做一些TypeScript类型相关的题目,这里记录一下自己的学习。
2022/05/11
4220
《现代Typescript高级教程》泛型和类型体操
泛型和类型体操(Type Gymnastics)是 TypeScript 中高级类型系统的重要组成部分。它们提供了强大的工具和技巧,用于处理复杂的类型操作和转换。
linwu
2023/07/27
6190
TypeScript 类型体操 - 进阶
映射类型的语法用于构造新的索引类型,在构造的过程中可以对索引和值做一些修改或过滤。
Cellinlab
2023/05/17
4120
编写TypeScript工具类型,你需要知道的知识
用 JavaScript 编写中大型程序是离不开 lodash 工具的,而用 TypeScript 编程同样离不开工具类型的帮助,工具类型就是类型版的 lodash 。简单的来说,就是把已有的类型经过类型转换构造一个新的类型。工具类型本身也是类型,得益于泛型的帮助,使其能够对类型进行抽象的处理。工具类型主要目的是简化类型编程的过程,提高生产力。
WahFung
2020/08/24
1.5K0
TypeScript进阶 之 重难点梳理
JavaScript 毋庸置疑是一门非常好的语言,但是其也有很多的弊端,其中不乏是作者设计之处留下的一些 “bug”。当然,瑕不掩瑜~
Nealyang
2020/03/25
4K0
TypeScript进阶 之 重难点梳理
TypeScript中的高级类型工具类型及关键字
本文主要帮助理解 TypeScript 中的高级类型及工具类型。在实际使用 TypeScript 的开发过程中,得益于这些高级类型于工具类型,我们可以更方便的构建出我们需要的类型。
路过的那只狗
2020/11/13
2.2K0
深入浅出 TypeScript
本文是阅读小册 「《深入浅出 TypeScript》」 的阅读笔记,对TypeScript感兴趣的同学请继续阅读吧。
chuckQu
2022/08/19
3K0
精读《ObjectEntries, Shift, Reverse...》
解决 TS 问题的最好办法就是多练,这次解读 type-challenges Medium 难度 41~48 题。
黄子毅
2022/11/21
5571
精读《Typescript infer 关键字》
理解为:如果 T 继承了 extends (...args: any[]) => any 类型,则返回类型 R,否则返回 any。其中 R 是什么呢?R 被定义在 extends (...args: any[]) => infer R 中,即 R 是从传入参数类型中推导出来的。
黄子毅
2022/03/15
8630
TypeScript条件类型(十)
TypeScript 2.8版本引入了条件类型(Conditional Types),TS条件类型可以进行类型选择,具体用法可以使用三元运算符实现,JS中的三元运算符用法一样,通过判断得到最终结果,TS条件类型最终得到的是数据类型。
不叫猫先生
2023/11/13
2980
TypeScript条件类型(十)
精读《type challenges - easy》
TS 强类型非常好用,但在实际运用中,免不了遇到一些难以描述,反复看官方文档也解决不了的问题,至今为止也没有任何一篇文档,或者一套教材可以解决所有犄角旮旯的类型问题。为什么会这样呢?因为 TS 并不是简单的注释器,而是一门图灵完备的语言,所以很多问题的解决方法藏在基础能力里,但你学会了基础能力又不一定能想到这么用。
黄子毅
2022/06/10
7030
TypeScript小笔记
比如各种框架的常用类型,ts中内置的常用类型,以及一些容易被忽略和遗忘的点,陆陆续续顺手把他们写到文章中记录起来。
19组清风
2021/11/15
1.1K0
TypeScript小笔记
深入解析 TypeScript 中的 infer 关键字及其实际应用
TypeScript 是一种功能强大的静态类型语言,其中 infer 关键字是条件类型中的一项独特功能。通过使用 infer,开发者可以从类型中推断信息,从而实现更动态和灵活的类型操作。
编程小妖女
2025/01/28
1530
深入解析 TypeScript 中的 infer 关键字及其实际应用
TypeScript进阶秘籍:类型体操的108种姿势
全系列终章:经过上面的探索,我们完成了从类型操作新手到类型体操高手的蜕变。建议将本系列作为参考手册,在实际项目中持续实践与优化。TypeScript的类型系统仍在快速发展,期待你在实践中发现更多精彩用法!
Jimaks
2025/04/19
1260
类型体操:探究 TypeScript 内置高级类型
TypeScript 的类型系统,最基本的是简单对应 JavaScript 的 基本类型,比如 string、number、boolean 等,然后是新增的 tuple、enum、复合类型、交叉类型、索引类型等 增强类型。
前端西瓜哥
2022/12/21
9410
类型体操:探究 TypeScript 内置高级类型
精读《Get return type, Omit, ReadOnly...》
解决 TS 问题的最好办法就是多练,这次解读 type-challenges Medium 难度 1~8 题。
黄子毅
2022/11/21
4700
Node.js 项目 TypeScript 改造指南(二)
最近笔者把一个中等规模的 Koa2 项目迁移到 TypeScript,和大家分享一下 TypeScript 实践中的经验和技巧。
WecTeam
2019/12/26
3.6K0
想去力扣当前端,TypeScript 需要掌握到什么程度?
2018 年底的时候,力扣发布了岗位招聘,其中就有前端,仓库地址:https://github.com/LeetCode-OpenSource/hire 。与大多数 JD 不同, 其提供了 5 道题, 并注明了: 完成一个或多个面试题,获取免第一轮面试的面试机会。完成的题目越多,质量越高,在面试中的加分更多。完成后的代码可以任意形式发送给 jobs@lingkou.com。以上几个问题完成一个或多个都有可能获得面试机会,具体情况取决于提交给我们的代码。
lucifer210
2020/07/10
1.3K0
相关推荐
TypeScript 体操,从进阶到放弃!
更多 >
目录
  • 用于条件判断时的 extends
  • 在泛型类型中使用条件类型
    • 工具类型
    • 逃离舱
  • 在箭头函数中使用条件类型
  • 结合类型推导使用条件类型
  • 使用条件类型来判断两个类型完全相等
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档