首页 > 解决方案 > 'extends'关键字在联合上的奇怪行为

问题描述

问题:

  1. 为什么类型M0M1以下不一样?
  2. 为什么 typeM0不完全等于 type A
  3. 具体是什么导致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)"

操场

注意:如果这与条件类型分配机制有关,请准确说明在上述上下文中是如何应用的。

谢谢!

标签: typescripttypescript-generics

解决方案


正如您正确直觉的那样,这与条件类型的分布有关。

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)'

是的,条件类型的分布确实破坏了引用透明度,因为它可以改变我们上面看到的操作顺序。要解析类型别名,必须解析所有类型参数。要解析条件类型,必须首先进行分发。因此,如果您将类型别名放在条件类型上,这很重要。


推荐阅读