你知道在 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…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/11457.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Esp32S3 | Arduino】在Arduino中使用C++的高级特性

文章目录 前言一、Arduino中的Vector示例代码二、Arduino中的Map示例代码前言 最近在玩Arduino,自上次发现Arduino可以用Template,能使用高级宏后,这次发现Arduino竟可以使用C++中的一些STL容器,这属实令人震惊。起因是我打算做一个动态的数组,但是手动实现一些操作属实麻烦…

polars学习-03 数据类型转换

背景 polars学习系列文章&#xff0c;第3篇 数据类型转换。 该系列文章会分享到github&#xff0c;大家可以去下载jupyter文件 仓库地址&#xff1a;https://github.com/DataShare-duo/polars_learn 小编运行环境 import sysprint(python 版本&#xff1a;,sys.version.spli…

Hack The Box-SolarLab

总体思路 SMB获取敏感信息->CVE-2023-33733漏洞注入->CVE-2023-32315->敏感信息泄露 信息收集&端口利用 nmap -sSVC -p1-10000 10.10.11.16发现目标开放了80、135、139、445和6791端口&#xff0c;并且对应的端口也给出了重定向的标志&#xff0c;将域名加入到…

实验过程演示【计算机网络实验】

前言 这是陈旧已久的草稿2023-05-20 11:23:54 这个是计算机网络的一个实验&#xff0c;现在也不知道这个是啥来着。 现在2024-5-12 22:33:17&#xff0c;发布到[计算机网络实验]专栏中。 实验过程演示 2023-5-18 20:17:45 1&#xff0e;搭建一个多跳网络拓扑&#xff0c;…

算法题解记录25+++验证二叉搜索树(百日筑基)

题目描述&#xff1a; 难度&#xff1a;中等 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下&#xff1a; 节点的左 子树 只包含 小于 当前节点的数。节点的右子树只包含 大于 当前节点的数。所有左子树和右子树自身必…

软件开发模型介绍

软件开发模型&#xff08;Software Development Model&#xff09;是指软件开发全部过程、活动和任务的结构框架。它清晰、直观地表达软件开发全过程&#xff0c;明确规定了要完成的主要活动和任务&#xff0c;用来作为软件项目工作的基础。 一、常见的软件开发模型&#xff1…

【MISRA-C-2012】:标准的理解与学习

标准的理解与学习 引用二、Misra-C 规则Misra-C全解读 - Rule 1 标准的C语言环境&#xff08;待更新&#xff09;Misra-C全解读 - Rule 2 未使用的代码&#xff08;待更新&#xff09;Misra-C全解读 - Rule 3 注释&#xff08;待更新&#xff09;Misra-C全解读 - Rule 4 字符与…

ThinkPHP+MySQL查询数据的时候计算两个经纬度之间的距离并根据距离进行筛选

原需求实现说明 新增了一个按距离进行筛选的需求。需要把查询代码做如下修改 /*** 求职意向* return void* throws \think\exception\DbException*/public function get_lists(){$request $this->request->get();if(empty($request[lng]) || empty($request[lat])){$th…

如何抠图?6个简单方便的抠图软件教你自己快速抠图

如何抠图&#xff1f;6个简单方便的抠图软件教你自己快速抠图 抠图是图像处理中常见的操作之一&#xff0c;它可以帮助我们从一幅图像中抠出特定的部分&#xff0c;通常用于制作合成图、更换背景或修改图像内容。下面介绍的6款简单方便的抠图软件可以帮助您快速进行抠图操作&a…

PyQt:界面无边框+实现窗口最小化(任务栏图标隐藏+托盘图标显示)

一、整体实现效果 诸如WX、各种管家的桌面显示方式。窗口关闭后&#xff0c;往往是任务栏图标消失&#xff0c;保持右下角托盘图标显示&#xff0c;保持后台运行。双击托盘图标后&#xff0c;窗口显示。 二、代码实现 from PyQt5.QtWidgets import * from ato_upgrade impo…

失效模式分析的适用范围与注意事项——SunFMEA软件

失效模式分析对产品从设计完成之后&#xff0c;到首次样品的发展而后生产制造&#xff0c;到品管验收等阶段都可说皆有许多适用范围&#xff0c;基本上可以活用在3个阶段。 一、设计阶段的失效模式分析 1.针对已设计的构想作为基础&#xff0c;逐项检讨系统的构造、机能上的问…

CSS常用滤镜效果

CSS 提供了多种滤镜效果&#xff0c;可以通过 filter 属性应用于 HTML 元素。以下是一些常用的 CSS 滤镜效果&#xff1a; 一、灰度 (Grayscale) 将图像转换为灰度图像。值在 0%&#xff08;原始图像&#xff09;和 100%&#xff08;完全灰度&#xff09;之间。 filter: gra…

qt信号和槽之间传送其他数据类型

提交信号和接受槽文件里分别全局声明该结构 Q_DECLARE_METATYPE (can) 在提交信号的时候将该数据结构set到QVariant里 在槽的接收里 &#xff0c;直接.value强转为声明的自定义结构里 void MainWindow::canrecvdeal(QVariant sy)//CAN_FRAME_MAG v { CAN_FRAME_MAG v; vsy.valu…

android进阶-回调

回调&#xff08;Callback&#xff09;是一种常见的编程模式&#xff0c;用于处理异步事件或信息传递。通过回调&#xff0c;一个对象&#xff08;通常是一个事件的发起者或处理者&#xff09;可以将某些任务或行为的执行通知给另一个对象 常见例子&#xff1a; 事件监听器&a…

Next.js+TS项目中的错误边界处理与渲染降级实践

在开发基于Next.js的TypeScript应用程序时&#xff0c;我们经常会遇到一些意料之外的JavaScript错误&#xff0c;这些错误可能会导致页面直接白屏&#xff0c;严重影响用户体验。为了提升应用的健壮性和用户体验&#xff0c;引入ErrorBoundary组件是一种非常有效的策略。本文将…

【回溯 栈 代数系统 动态规划】282. 给表达式添加运算符

本文涉及知识点 回溯 栈 代数系统 动态规划 LeetCode 282. 给表达式添加运算符 给定一个仅包含数字 0-9 的字符串 num 和一个目标值整数 target &#xff0c;在 num 的数字之间添加 二元 运算符&#xff08;不是一元&#xff09;、- 或 * &#xff0c;返回 所有 能够得到 ta…

Rust 中的声明可见性

Rust 中的声明可见性 在 Rust 编程语言中&#xff0c;声明可见性是一个核心概念&#xff0c;它决定了代码中的项&#xff08;如函数、结构体、枚举等&#xff09;在哪些范围内可以被访问。Rust 通过一套严谨的规则来控制这些可见性&#xff0c;以确保代码的安全性和封装性。下…

Ngnix VTS模块添加和测试

目录 VTS模块介绍 上传软件包xftp/lrzsz 执行脚本 添加vts的配置 测试 测试&#xff1a;nginx.conf配置文件是否有语法错误 测试&#xff1a;windows机器上访问效果 VTS模块介绍 Nginx VTS模块&#xff08;nginx Virtual Host Traffic Status Module&#xff09;是一个第三…

【C++初阶】string模拟实现

✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅ ✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨ &#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1…

【精读Yamamoto】方向性连接如何丰富神经网络的功能复杂度 | 体外神经元培养实验 | 脉冲神经元模型(SNN) | 状态转移模型

探索大脑的微观世界&#xff1a;方向性连接如何丰富神经网络的功能复杂度 在神经科学领域&#xff0c;理解大脑如何通过其复杂的网络结构实现高级功能一直是一个核心议题。最近&#xff0c;一项由Nobuaki Monma和Hideaki Yamamoto博士领导的研究为我们提供了新的视角&#xff…