TypeScript中条件类型精读与实践记录


Posted in Javascript onOctober 05, 2021

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

用于条件判断时的 extends

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

若位于 extends 两侧的类型相同,则 extends 在语义上可理解为 ===,可以参考如下例子:

type result1 = 'a' extends 'abc' ? true : false // false
type result2 = 123 extends 1 ? true : false     // false

若位于 extends 右侧的类型包含位于 extends 左侧的类型(即狭窄类型 extends 宽泛类型)时,结果为 true,反之为 false。可以参考如下例子:

type result3 = string extends string | number ? true : false // true

当 extends 作用于对象时,若在对象中指定的 key 越多,则其类型定义的范围越狭窄。可以参考如下例子:

type result4 = { a: true, b: false } extends { a: true } ? true : false // true

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

考虑如下 Demo 类型定义:

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'。用伪代码表示类似于:

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 类型之外。

type T = Demo<'a' | 'b' | 'c', 'a'> // T: 'b' | 'c'

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

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.

如果不想遍历泛型中的每一个类型,可以用方括号将泛型给括起来以表示使用该泛型的整体部分。
type Demo<T, U> = [T] extends [U] ? never : T

type Demo<T, U> = [T] extends [U] ? never : T

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

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

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

// The intent is not clear.
var x = a => 1 ? true : false

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

var x = a => (1 ? true : false)

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

type Curry<P extends any[], R> =
  (arg: Head<P>) => HasTail<P> extends true ? Curry<Tail<P>, R> : R

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

type Curry<P extends any[], R> =
  (arg: Head<P>) => (HasTail<P> extends true ? Curry<Tail<P>, R> : R)

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

在 TypeScript 中,一般会结合 extends 来使用类型推导 infer 语法。使用它可以实现自动推导类型的目的。比如用其来实现工具类型 ReturnType<Type> ,该工具类型用于返回函数 Type 的返回类型。
type ReturnType<T extends Function> = T extends (...args: any) => infer U ? U : never

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>:

type Pop<T extends any[]> = T extends [...infer ExceptLast, any] ? ExceptLast : never

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

Shift<T>:

type Shift<T extends any[]> = T extends [infer _, ...infer O] ? O : never

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

Reverse<T>

type Reverse<T> = T extends [infer F, ...infer Others]
  ? [...Reverse<Others>, F]
  : []

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

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

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

方案一: 参考 issue

export type Equal1<T, S> =
  [T] extends [S] ? (
    [S] extends [T] ? true : false
  ) : false

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

type T = Equal1<{x:any}, {x:number}> // T: true

方案二: 参考 issue

export type Equal2<X, Y> =
  (<T>() => T extends X ? 1 : 2) extends
  (<U>() => U extends Y ? 1 : 2) ? true : false

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

type T = Equal2<{x:1} & {y:2}, {x:1, y:2}> // false

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

总结

到此这篇关于TypeScript中条件类型精读与实践的文章就介绍到这了,更多相关TypeScript条件类型内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
我遇到的参数传递中 双引号单引号嵌套问题
Feb 11 Javascript
JavaScrip单线程引擎工作原理分析
Sep 04 Javascript
仅IE9/10同时支持script元素的onload和onreadystatechange事件分析
Apr 27 Javascript
Prototype的Class.create函数解析
Sep 22 Javascript
jQuery中animate()方法用法实例
Dec 24 Javascript
自己编写的支持Ajax验证的JS表单验证插件
May 15 Javascript
深入剖析JavaScript编程中的对象概念
Oct 21 Javascript
原生JS实现平滑回到顶部组件
Mar 16 Javascript
JavaScript、C# URL编码、解码总结
Jan 21 Javascript
Angularjs分页查询的实现
Feb 24 Javascript
使用webpack编译es6代码的方法步骤
Apr 28 Javascript
JavaScript 防篡改对象的用法示例
Apr 24 Javascript
SSM VUE Axios详解
Ajax实现三级联动效果
Oct 05 #Javascript
5种 JavaScript 方式实现数组扁平化
Oct 05 #Javascript
国庆节到了,利用JS实现一个生成国庆风头像的小工具 详解实现过程
Oct 05 #Javascript
Javascript设计模式之原型模式详细
JS数组方法some、every和find的使用详情
8个JS的reduce使用实例和reduce操作方式
Oct 05 #Javascript
You might like
PDO版本问题 Invalid parameter number: no parameters were bound
2013/01/06 PHP
PHP date()格式MySQL中插入datetime方法
2019/01/29 PHP
thinkphp5框架扩展redis类方法示例
2019/05/06 PHP
php的对象传值与引用传值代码实例讲解
2021/02/26 PHP
jQuery的一些注意
2006/12/06 Javascript
js获取div高度的代码
2008/08/09 Javascript
JQuery从头学起第一讲
2010/07/04 Javascript
js和php如何获取当前url的内容
2013/09/22 Javascript
jQuery Migrate 1.1.0 Released 注意事项
2014/06/14 Javascript
nodejs实现的一个简单聊天室功能分享
2014/12/06 NodeJs
JQuery插件Quicksand实现超炫的动画洗牌效果
2015/05/03 Javascript
jQuery form插件之ajaxForm()和ajaxSubmit()的可选参数项对象
2016/01/23 Javascript
AngularJs基于角色的前端访问控制的实现
2016/11/07 Javascript
微信小程序 网络API Websocket详解
2016/11/09 Javascript
vue中使用echarts制作圆环图的实例代码
2018/07/27 Javascript
你或许不知道的一些npm实用技巧
2019/07/04 Javascript
jQuery实现提交表单时不提交隐藏div中input的方法
2019/10/08 jQuery
JS实现随机抽选获奖者
2019/11/07 Javascript
vue打开子组件弹窗都刷新功能的实现
2020/09/21 Javascript
[06:49]2018DOTA2国际邀请赛寻真——VirtusPro傲视群雄
2018/08/12 DOTA
python多重继承实例
2014/10/11 Python
简单介绍Python的Tornado框架中的协程异步实现原理
2015/04/23 Python
python中使用PIL制作并验证图片验证码
2018/03/15 Python
Python3 实现串口两进程同时读写
2019/06/12 Python
Python使用字典实现的简单记事本功能示例
2019/08/15 Python
Python入门基础之数字字符串与列表
2021/02/01 Python
python 如何读、写、解析CSV文件
2021/03/03 Python
趣天网日本站:Qoo10 JP
2019/09/18 全球购物
2014的自我评价
2014/01/13 职场文书
党支部书记先进事迹
2014/01/17 职场文书
《吃水不忘挖井人》教学反思
2014/04/15 职场文书
法人任命书范本
2014/06/04 职场文书
喜迎建国70周年:有关爱国的名言名句
2019/09/24 职场文书
Spring Boot项目传参校验的最佳实践指南
2022/04/05 Java/Android
Python中time标准库的使用教程
2022/04/13 Python
python+opencv实现目标跟踪过程
2022/06/21 Python