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 相关文章推荐
JavaScript页面刷新与弹出窗口问题的解决方法
Mar 02 Javascript
腾讯的ip接口 方便获取当前用户的ip地理位置
Nov 25 Javascript
Extjs中TabPane如何嵌套在其他网页中实现思路及代码
Jan 27 Javascript
JavaScript 创建运动框架的实现代码
May 08 Javascript
Jquery多选下拉列表插件jquery multiselect功能介绍及使用
May 24 Javascript
form.submit()不能提交表单的错误原因及解决方法
Oct 13 Javascript
javascript解三阶幻方(九宫格)
Apr 22 Javascript
jQuery validate+artdialog+jquery form实现弹出表单思路详解
Apr 18 Javascript
jQuery实现弹窗居中效果类似alert()
Feb 27 Javascript
详解vue 计算属性与方法跟侦听器区别(面试考点)
Apr 23 Javascript
Vue 实时监听窗口变化 windowresize的两种方法
Nov 06 Javascript
javascript实现移动端红包雨页面
Jun 23 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
下载文件的点击数回填
2006/10/09 PHP
php实现mysql数据库连接操作及用户管理
2015/11/08 PHP
javascript String 的扩展方法集合
2008/06/01 Javascript
jquery中通过过滤器获取表单元素的实现代码
2011/07/05 Javascript
JQuery通过Ajax提交表单并返回结果
2011/07/31 Javascript
jquery插件开发方法(初学者)
2012/02/03 Javascript
通过JavaScript使Div居中并随网页大小改变而改变
2013/06/24 Javascript
JS实现关键字搜索时的相关下拉字段效果
2014/08/05 Javascript
JS实现CheckBox复选框全选全不选功能
2015/05/06 Javascript
jQuery检测某个元素是否存在代码分享
2015/07/09 Javascript
jQuery的文档处理程序详解
2016/05/10 Javascript
功能强大的Bootstrap效果展示(二)
2016/08/03 Javascript
JavaScript实现大图轮播效果
2017/01/11 Javascript
bootstrap 模态框(modal)实现水平垂直居中显示
2017/01/23 Javascript
分享十三个最佳JavaScript数据网格库
2017/04/07 Javascript
前端构建工具之gulp的语法教程
2017/06/12 Javascript
微信小程序与php 实现微信支付的简单实例
2017/06/23 Javascript
vue2.0 资源文件assets和static的区别详解
2018/04/08 Javascript
jQuery实现动态添加和删除input框实例代码
2019/03/26 jQuery
解决vue prop传值default属性如何使用,为何不生效的问题
2020/09/21 Javascript
[01:04:48]VGJ.S vs TNC Supermajor 败者组 BO3 第一场 6.6
2018/06/07 DOTA
python获取本地计算机名字的方法
2015/04/29 Python
Python用UUID库生成唯一ID的方法示例
2016/12/15 Python
Python3 单行多行万能正则匹配方法
2019/01/07 Python
python协程gevent案例 爬取斗鱼图片过程解析
2019/08/27 Python
Python嵌套函数,作用域与偏函数用法实例分析
2019/12/26 Python
浅谈tensorflow中Dataset图片的批量读取及维度的操作详解
2020/01/20 Python
Python Django form 组件动态从数据库取choices数据实例
2020/05/19 Python
JavaScript获取当前url根目录(路径)
2014/02/19 面试题
通信工程专业毕业生推荐信
2013/12/25 职场文书
纪念九一八事变演讲稿:牢记历史,捍卫主权
2014/09/14 职场文书
教师三严三实对照检查材料
2014/09/25 职场文书
小学教师学习党的群众路线教育实践活动心得体会
2014/10/31 职场文书
房屋授权无偿使用证明
2014/11/29 职场文书
商场圣诞节活动总结
2015/05/06 职场文书
MySQL事务的隔离级别详情
2022/07/15 MySQL