簡體   English   中英

Typescript 使用聯合推斷通用參數類型

[英]Typescript infer generic param types with union

我一直在為下面的代碼苦苦掙扎幾個小時。 不明白為什么e4string而不是String

type PropConstructor4<T = any> = { new(...args: any[]): (T & object) } | { (): T }
type e4 = StringConstructor extends PropConstructor4<infer R> ? R : false // why string not String ???

我在下面進行了測試,我想我可以理解。

type a4 = StringConstructor extends { new(...args: any[]): (infer R & object) } ? R : false // String
type b4 = StringConstructor extends { (): ( String) } ? true : false // true
type c4 = StringConstructor extends { (): (infer R) } ? R : false // string

另外,我不明白為什么e5String不是string

type PropConstructor5<T = any> = { new(...args: any[]): (T ) } | { (): T }
type e5 = StringConstructor extends PropConstructor5<infer R> ? R : false //why String not string??

TL;DR 編譯器使用啟發式方法為不同的推理站點賦予不同的優先級,並從具有最高優先級的推理站點推斷類型。


一般來說,當 TypeScript 推斷類型參數的特定類型(所有示例中的R )時,它會考慮每個推理站點,這是類型參數出現在它試圖匹配的表達式中的位置。 例如,在

type P = StringConstructor extends 
    (() => infer R) | { new(...args: any[]): (infer R & object) } ? R : never
//         ^^^^^^^  <-- inference sites -->   ^^^^^^^

R類型參數有兩個推理站點。 編譯器的工作是嘗試通過檢查推理站點,為該站點提供候選特定類型,然后檢查完整表達式以確定該候選在每個站點中替換時是否有效,從而嘗試將StringConstructor與完整表達式匹配。

讓我們用上面的P來測試它,假設我們是編譯器,看看如果我們改變要檢查推理站點會發生什么。


如果編譯器選擇第一個推理站點進行檢查:

type P = StringConstructor extends 
    (() => infer R) | { new(...args: any[]): (infer R & object) } ? R : never
//         ^^^^^^^  <-- inspect this

在這種情況下, string是它想出的候選,因為String("hello")產生一個string output。 然后它可以檢查string是否適用於整個表達式。 StringConstructor確實擴展了(() => string) | { new(...args: any[]): (string & object) } (() => string) | { new(...args: any[]): (string & object) }因為它擴展了聯合的第一個成員(因為A extends B | C如果A extends BA extends C ),因此R如果編譯器僅考慮第一個推理站點,則將其推斷為string


第二個推理站點呢?

type P = StringConstructor extends 
    (() => infer R) | { new(...args: any[]): (infer R & object) } ? R : never
//                           inspect this --> ~~~~~~~~

在這種情況下, String是它會提出的候選,因為new String("hello")產生一個String output。 然后它會檢查String是否適用於整個表達式。 StringConstructor確實擴展了(() => String) | { new(...args: any[]): (String & object) } (() => String) | { new(...args: any[]): (String & object) }因為它擴展了聯合的兩邊。 ( string extends String ,所以() => string extends () => String ),如果編譯器只考慮第二個推理站點,那么R將被推斷為String


編譯器也有可能同時考慮兩個推理位置,並根據方差合成候選的聯合/超類型或交集/子類型。 在這種情況下,參數都在協變 position 中,所以string | String 如果發生這種情況,我會猜測string | String或只是String (因為它是string的超類型)。


那么對於上面的表達式,人們可以合理地想象string , String , string | String string | String出來。 實際發生了什么?

type P = StringConstructor extends
    (() => infer R) | { new(...args: any[]): (infer R & object) } ? R : never
// string

string 這意味着編譯器優先考慮第一個推理站點。 比較以下發生的情況:

type O = StringConstructor extends
    (() => infer R) | { new(...args: any[]): (infer R) } ? R : never
// String

現在,編譯器優先考慮第二個推理站點。 不知何故, (infer R & object)的優先級低於僅infer R


那么,編譯器如何為不同的推理站點分配優先級呢? 我不能假裝知道這件事的全部細節。

曾經在 TypeScript 規范文檔中列出,但該文檔早已過時,現已存檔。 如果您好奇,請參閱本節 現在沒有規范,因為語言的變化速度超過了嚴格記錄的速度。

GitHub 中有一些問題涉及推理站點優先級的概念。 請參閱microsoft/TypeScript#14829以獲取允許站點具有零優先級並且從不用於推理的功能請求。 請參閱microsoft/TypeScript#39295microsoft/ TypeScript#32389,了解源自開發人員的直覺與編譯器實際執行的操作不同的問題。

一個常見的線程是,像(T & {})這樣的交集的優先級低於沒有交集T的類型。 因此,如果站點妨礙您,您可以使用T & {}降低站點的優先級。 所以你解釋了為什么infer R & object不是P的選擇站點。

再說一次,我不知道這個的全部細節,學習它可能不是很有啟發性。 如果您通過類型檢查器代碼go 可能能夠拼湊出構造簽名返回類型是否比調用簽名返回類型具有更高的優先級,但我不建議編寫任何依賴於此類細節的程序,除非您想繼續重新訪問它...不能保證這種特定的推理規則會在語言的各個版本中持續存在。


Playground 代碼鏈接

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM