typescript - 'extends'关键字在联合上的奇怪行为
问题描述
问题:
- 为什么类型
M0
和M1
以下不一样? - 为什么 type
M0
不完全等于 typeA
? - 具体是什么导致type缺乏引用透明度
Ext
?
请根据以下示例进行说明:
// Given
type A = '(T extends U) and (U extends T)'
type B = '(T extends U) and (U NOT extends T)'
type C = '(T NOT extends U) and (U extends T)'
type D = '(T NOT extends U) and (U NOT extends T)'
type Ext<T,U,X,Y> = T extends U ? X : Y
//1
type ExtendsCheck<T,U> = T extends U ? (U extends T ? A : B) : (U extends T ? C : D)
//2
type ExtendsCheck_<T,U> = Ext<T,U,Ext<U,T,A, B>,Ext<U,T,C,D>>
// Why
type T = 'foo' | 'bar'
type U = T
type M0 = ExtendsCheck<T,U> // M0: "(T extends U) and (U extends T)" | "(T extends U) and (U NOT extends T)"
type M1 = ExtendsCheck_<T,U> // M1: "(T extends U) and (U extends T)"
注意:如果这与条件类型分配机制有关,请准确说明在上述上下文中是如何应用的。
谢谢!
解决方案
正如您正确直觉的那样,这与条件类型的分布有关。
TL;DR:这一切都归结为何时应用分发。在第一种情况下,ExtendsCheck
先分配 over T
,然后再分配 over U
。在第二种情况下Ext<U,T,A, B>
,Ext<U,T,C,D>
并且首先解决,因此U
首先发生分配,然后解决外部,然后发生Ext
分配T
。
让我们考虑这两种类型的评估顺序:
ExtendsCheck<T = 'foo' | 'bar',U ='foo' | 'bar'> =
=> T extends U ? (U extends T ? A : B) : (U extends T ? C : D)
// Distribute over T (since T extends U is a condition over a naked type parameter)
=> ('foo' extends U ? (U extends 'foo' ? A : B) : (U extends 'foo' ? C : D)) |
('bar' extends U ? (U extends 'bar' ? A : B) : (U extends 'bar' ? C : D))
// Resolve the first conditional in each distribution result, both are true
=> (U extends 'foo' ? A : B) |
(U extends 'bar' ? A : B)
// Distribute over U (since U extends T is a condition over a naked type parameter)
=> ('foo' extends 'foo' ? A : B) | ('bar' extends 'foo' ? A : B) |
('foo' extends 'bar' ? A : B) | ('bar' extends 'bar' ? A : B)
// Resolve the conditional types
=> A | B | A | B
// Union reduction
=> A | B
=> '(T extends U) and (U extends T)' | '(T extends U) and (U NOT extends T)'
对于第二种类型,情况有所不同
type ExtendsCheck_<T= 'foo' | 'bar',U ='foo' | 'bar'>
=> Ext<T,U, Ext<U,T,A, B>, Ext<U,T,C,D>>
// Resolve Ext<U,T,A, B> for U = T = 'foo' | 'bar'
Ext<U = 'foo' | 'bar', T = 'foo' | 'bar'>
=> U extends T ? A : B
// Distribute over U
=> ('foo' extends 'foo' | 'bar' ? A : B) | ('bar' extends 'foo' | 'bar' ? A : B)
// Both are true
=> A | A
// Union reduction
=> A
// Resolve Ext<U,T,C,D> for U = T = 'foo' | 'bar'
Ext<U = 'foo' | 'bar', T = 'foo' | 'bar'>
=> U extends T ? C : D
// Distribute over U
=> ('foo' extends 'foo' | 'bar' ? C : D) | ('bar' extends 'foo' | 'bar' ? C : D)
// Both are true
=> C | C
// Union reduction
=> C
// So we get Ext<T, U, A, C> for U = T = 'foo' | 'bar'
=> Ext<T = 'foo' | 'bar', U = 'foo' | 'bar', A, C>
=> T extends U ? A : C
// Distribute over T
=> ('foo' extends 'foo' | 'bar' ? A : C) | ( 'bar' extends 'foo' | 'bar'? A : C)
// Resolve the conditionals, they are both true
=> A | A
// Union reduction
=> A
=> '(T extends U) and (U extends T)'
是的,条件类型的分布确实破坏了引用透明度,因为它可以改变我们上面看到的操作顺序。要解析类型别名,必须解析所有类型参数。要解析条件类型,必须首先进行分发。因此,如果您将类型别名放在条件类型上,这很重要。
推荐阅读
- dialogflow-es - Dialogflow 无法覆盖实体
- android - 根据所选应用分享不同的文字
- spring-boot - 我如何在 Springdoc-swagger-ui 中获取 RequestBody null 或 empty 中的属性?
- scala - spark(scala)中字节数组数据帧列的startsWith函数是否有模拟?
- r - 根据R中的条件合并多列
- java - 将 !=/== 与 Character 一起使用是否安全?
- c++ - 如何将变量声明为 char* const*?
- arrays - Swift - 给定一个固定的字符串数组(次),找到下一个最接近的时间
- python - 我如何从这些页面中抓取网站的链接
- python - 通过映射双索引 groupby 对象填充数据框中的 NA 值