你知道在 TS 中判断两个类型相等有多难吗?

2024-05-14 02:52

本文主要是介绍你知道在 TS 中判断两个类型相等有多难吗?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

公众号:程序员白特,欢迎一起交流学习~

TypeScript 中的类型相等

如果我们想判断两个变量是否相等,可以简单的通过 ===== 来进行比较,但是对比两个类型则不行。

在 TypeScript 中,类型是静态的,只会在编译时进行类型检查。

如果我们有两个类型 AB,我们直接比较两个类型是否相等则会报错:

type A = number;
type B = string;
type C = number == string; // 'string' only refers to a type, but is being used as a value here.ts(2693)

我们可以看到 TypeScript 提醒我们不能把类型当做值来用。

那我们如何判断两个类型是否相等呢?用的比较广泛的是 GitHub [Feature request]type level equal operator 中一位大神提到的 :

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

这段代码虽然很好用,但是原理却让人一头雾水,所以我尝试来分析下它到底是如何运作的。

条件类型

不了解Typescript类型的人可能对T extends X ? 1 : 2的含义不太了解,实际上这是TS中的条件类型。

在Typescript中,有一个特性叫“条件类型(Conditional Types)”,条件类型的形式有点像JavaScript中的条件表达式(condition ? trueExpression : falseExpression):.

SomeType extends OtherType ? TrueType : FalseType;

当 extends 左边的类型可以赋值给右边的类型时,我们会得到第一个分支的类型 TrueType,否则得到后面的类型 FalseType

举个例子:

interface Animal {live(): void;
}
interface Dog extends Animal {woof(): void;
}
type Example1 = Dog extends Animal ? number : string; // number
type Example2 = RegExp extends Animal ? number : string; // string

那么接下来的问题是,既然条件类型判断的是一个类型是否能复制给另一个类型,那么如何确定是否可以赋值呢?我们需要深入了解一下Typescript的可赋值性。

Typescript 的可赋值性

为了了解 Typescript 的可赋值性,我专门找了一篇文章,并翻译了一下:[译]TypeScript 的可赋值性 简单总结一下:

  1. 如果两个类型相等,那么它们可以相互赋值。

  2. 判断简单类型的可赋值性就是判断它们是否相等。

  3. 对于对象类型的可赋值性,如果一个类型是另一个类型的超类型(即包含了另一个类型的所有成员),那么这个类型的值可以赋给另一个类型的变量。

    var source: { a: number, b: string };
    var target: { a: number };
    target = source;
    
  4. 函数类型的可赋值性是一个比较复杂的概念,需要考虑函数的参数类型和返回类型。在判断返回值时,需要确保原函数的返回值可以赋值给目标函数的返回值;而在比较参数时,则需要以逆变的方式进行,即确保目标函数的参数可以赋值给原函数的参数。举个例子来说明可能会更容易理解:

    var source: (a: string) => void;
    var target: (a: unknown) => void;
    target = source; // should be an error, because:
    target(1); // oops, can't pass numbers to source
    

这篇文章还有好多内容还没有讲,作者最后说 可以在 GitHub 上的 TypeScript 仓库中的 src/compiler/checker.ts 文件中查看 checkTypeRelatedTo 函数。

现在我们分析类型相等的代码:

(<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2)? true : false;

可以观察到,需要判断两个函数类型的可赋值性:(<T>() => T extends X ? 1 : 2) 是否可以赋值给 extends (<T>() => T extends Y ? 1 : 2)。这两个函数没有参数,我们只需要考虑返回值是否可以赋值。也就是说,条件类型 T extends X ? 1 : 2 是否可以赋值给条件类型 T extends Y ? 1 : 2

条件类型的可赋值性

如何判断一个条件类型可以赋值给另一个条件类型呢?在 Typescript 的仓库中 src/compiler/checker.ts 我们找到了 checkTypeRelatedTo 函数:

这个函数用于检查源类型 source 是否与目标类型 target 相关,其中关系 relation 可以是 identityRelation(恒等关系),subtypeRelation(子类型关系),assignableRelation(可赋值关系),或 comparableRelation(可比较关系)。

然后在其中找到了关于条件类型的判断代码链接:

根据注释,如果有两个条件类型 T1 extends U1 ? X1 : Y1T2 extends U2 ? X2 : Y2,它们被认为是相关的,需要满足以下条件:

  1. T1T2 中的一个与另一个相关。这意味着 T1 可以赋值给 T2 或者 T2 可以赋值给 T1
  2. U1U2 是相同的类型。
  3. X1 可以赋值 X2
  4. Y1 可以赋值 Y2

对于源代码中的泛型 T 我们对其都没有任何约束,我理解他们两个是具有可赋值性的(这里我并不确定,仅主观猜测)。

接下来,我们再来看一下 IsEqual 代码:

type IsEqual<X, Y> =(<T>() => T extends X ? 1 : 2) extends(<T>() => T extends Y ? 1 : 2) ? true : false;

此时我们可以发现,当 XY 相等,(<T>() => T extends X ? 1 : 2) 可以赋值给 (<T>() => T extends Y ? 1 : 2) ,所以 IsEqualtrue,反之则为 false

小结

TypeScript 类型越研究越发现有非常多的内容,包括本文讨论的 Equal 其实已经涉及到编译器相关源码,能够提出这个函数的人肯定是对 TS 非常了解才能举重若轻地写出这个有些 hack 的代码,不过还是希望 TS 官方早日能给出 Equal 函数,减轻大家的心智负担。😃

参考资料

  • [Feature request] type level equal operator
  • Conditional Types
  • www.zhihu.com/question/57…

这篇关于你知道在 TS 中判断两个类型相等有多难吗?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/987549

相关文章

java中判断json key是否存在的几种方法

《java中判断jsonkey是否存在的几种方法》在使用Java处理JSON数据时,如何判断某一个key是否存在?本文就来介绍三种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的... 目http://www.chinasem.cn录第一种方法是使用 jsONObject 的 has 方法

JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法

《JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法》:本文主要介绍JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法,每种方法结合实例代码给大家介绍的非常... 目录引言:为什么"相等"判断如此重要?方法1:使用some()+includes()(适合小数组)方法2

如何通过try-catch判断数据库唯一键字段是否重复

《如何通过try-catch判断数据库唯一键字段是否重复》在MyBatis+MySQL中,通过try-catch捕获唯一约束异常可避免重复数据查询,优点是减少数据库交互、提升并发安全,缺点是异常处理开... 目录1、原理2、怎么理解“异常走的是数据库错误路径,开销比普通逻辑分支稍高”?1. 普通逻辑分支 v

Python中Json和其他类型相互转换的实现示例

《Python中Json和其他类型相互转换的实现示例》本文介绍了在Python中使用json模块实现json数据与dict、object之间的高效转换,包括loads(),load(),dumps()... 项目中经常会用到json格式转为object对象、dict字典格式等。在此做个记录,方便后续用到该方

从基础到进阶详解Python条件判断的实用指南

《从基础到进阶详解Python条件判断的实用指南》本文将通过15个实战案例,带你大家掌握条件判断的核心技巧,并从基础语法到高级应用一网打尽,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一... 目录​引言:条件判断为何如此重要一、基础语法:三行代码构建决策系统二、多条件分支:elif的魔法三、

python中的显式声明类型参数使用方式

《python中的显式声明类型参数使用方式》文章探讨了Python3.10+版本中类型注解的使用,指出FastAPI官方示例强调显式声明参数类型,通过|操作符替代Union/Optional,可提升代... 目录背景python函数显式声明的类型汇总基本类型集合类型Optional and Union(py

MySQL中查询和展示LONGBLOB类型数据的技巧总结

《MySQL中查询和展示LONGBLOB类型数据的技巧总结》在MySQL中LONGBLOB是一种二进制大对象(BLOB)数据类型,用于存储大量的二进制数据,:本文主要介绍MySQL中查询和展示LO... 目录前言1. 查询 LONGBLOB 数据的大小2. 查询并展示 LONGBLOB 数据2.1 转换为十

MyBatis的xml中字符串类型判空与非字符串类型判空处理方式(最新整理)

《MyBatis的xml中字符串类型判空与非字符串类型判空处理方式(最新整理)》本文给大家介绍MyBatis的xml中字符串类型判空与非字符串类型判空处理方式,本文给大家介绍的非常详细,对大家的学习或... 目录完整 Hutool 写法版本对比优化为什么status变成Long?为什么 price 没事?怎

C#之枚举类型与随机数详解

《C#之枚举类型与随机数详解》文章讲解了枚举类型的定义与使用方法,包括在main外部声明枚举,用于表示游戏状态和周几状态,枚举值默认从0开始递增,也可手动设置初始值以生成随机数... 目录枚举类型1.定义枚举类型(main外)2.使用生成随机数总结枚举类型1.定义枚举类型(main外)enum 类型名字

Python lambda函数(匿名函数)、参数类型与递归全解析

《Pythonlambda函数(匿名函数)、参数类型与递归全解析》本文详解Python中lambda匿名函数、灵活参数类型和递归函数三大进阶特性,分别介绍其定义、应用场景及注意事项,助力编写简洁高效... 目录一、lambda 匿名函数:简洁的单行函数1. lambda 的定义与基本用法2. lambda