ข้ามไปยังเนื้อหา

Inference & infer

เมื่อคุณเรียก generic function โดยไม่ส่ง type argument TypeScript จะดูทุกที่ที่ type parameter ปรากฏใน parameter — แต่ละที่คือ inference site — แล้วคำนวณ type ที่เข้ากับทุกที่ได้

function pair<A, B>(a: A, b: B): [A, B] {
return [a, b];
}
const p = pair(1, "hi");
// ^? [number, string] — A from the first arg, B from the second

เมื่อ parameter หนึ่งปรากฏมากกว่าหนึ่งครั้ง TypeScript จะประนีประนอมระหว่าง candidate:

function both<T>(a: T, b: T): T { return a; }
const x = both(1, 2); // T = number
const y = both(1, "two"); // T = number | string (best common type)

การเข้าใจว่า inference อ่าน ตำแหน่งของ argument อธิบายโมเมนต์ “ทำไม compiler infer แบบนั้น” ได้เกือบทั้งหมด — และอธิบายว่าทำไมการ annotate return type ไม่เคยเปลี่ยนสิ่งที่ argument infer ออกมา

ภายใน conditional type keyword infer แนะนำ type variable ใหม่ที่ TypeScript แก้ให้คุณ ด้วยการ pattern-match structure นี่คือวิธีดึงชิ้นส่วนออกจาก type ที่ใหญ่กว่า

ตัวอย่างคลาสสิก — เอา return type ของ function:

type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type A = MyReturnType<() => number>; // number
type B = MyReturnType<(x: string) => User>; // User
type C = MyReturnType<string>; // never (not a function)

อ่าน infer R ว่า “match type นี้กับ pattern (...args) => R ถ้าเข้ากันได้ ให้ bind R เป็นอะไรก็ตามที่อยู่ในตำแหน่ง return” นี่คือวิธีที่ built-in ReturnType<T> ถูกนิยามเป๊ะ

infer จับตำแหน่งไหนใน structure ก็ได้ — element type, promise value, argument type:

// element type of an array
type ElementType<T> = T extends (infer E)[] ? E : T;
type E1 = ElementType<number[]>; // number
type E2 = ElementType<string>; // string (unchanged — not an array)
// the value a Promise resolves to
type Awaited1<T> = T extends Promise<infer V> ? V : T;
type V1 = Awaited1<Promise<string>>; // string
// the first parameter type
type FirstArg<T> = T extends (first: infer P, ...rest: any[]) => any ? P : never;
type P1 = FirstArg<(id: number) => void>; // number

ทุกอันคือการทำแบบเดียวกัน: บรรยาย shape ที่คุณคาดหวัง วาง infer X ตรงที่ส่วนที่น่าสนใจอยู่ แล้ว TypeScript เติม X ให้เมื่อ shape เข้ากัน

จุดที่ต้องระวัง: infer หลายตัวในตำแหน่งเดียวกัน

หัวข้อที่มีชื่อว่า “จุดที่ต้องระวัง: infer หลายตัวในตำแหน่งเดียวกัน”

เมื่อชื่อ infer เดียวกันปรากฏหลายจุด พฤติกรรมของ TypeScript ขึ้นกับตำแหน่ง — โดย infer เป็น union ในตำแหน่ง covariant และเป็น intersection ในตำแหน่ง contravariant (parameter) คุณไม่ค่อยต้องใช้ แต่อธิบายเซอร์ไพรส์บางอย่าง:

type Merge<T> = T extends { a: infer U; b: infer U } ? U : never;
type M = Merge<{ a: string; b: number }>; // string | number (union)

สำหรับงานทั่วไปในแต่ละวัน infer หนึ่งตัวต่อ pattern ก็พอแล้ว

TypeScript เอาค่าของ type parameter ของ generic function มาจากไหน?
`infer R` ใน `T extends (...args: any[]) => infer R ? R : never` ทำอะไร?
จะดึง element type ออกจาก array type อย่างไร?
built-in `ReturnType<T>` ถูกสร้างด้วย: