Inference & infer
inference เอาคำตอบมาจากไหน: inference site
หัวข้อที่มีชื่อว่า “inference เอาคำตอบมาจากไหน: inference site”เมื่อคุณเรียก 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 = numberconst y = both(1, "two"); // T = number | string (best common type)การเข้าใจว่า inference อ่าน ตำแหน่งของ argument อธิบายโมเมนต์ “ทำไม compiler infer แบบนั้น” ได้เกือบทั้งหมด — และอธิบายว่าทำไมการ annotate return type ไม่เคยเปลี่ยนสิ่งที่ argument infer ออกมา
infer: ดึง type ออกมาจากข้างใน type อื่น
หัวข้อที่มีชื่อว่า “infer: ดึง type ออกมาจากข้างใน type อื่น”ภายใน 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>; // numbertype B = MyReturnType<(x: string) => User>; // Usertype C = MyReturnType<string>; // never (not a function)อ่าน infer R ว่า “match type นี้กับ pattern (...args) => R ถ้าเข้ากันได้ ให้ bind R เป็นอะไรก็ตามที่อยู่ในตำแหน่ง return” นี่คือวิธีที่ built-in ReturnType<T> ถูกนิยามเป๊ะ
pattern นี้ใช้ได้ทั่วไป: ดึงส่วนไหนออกมาก็ได้
หัวข้อที่มีชื่อว่า “pattern นี้ใช้ได้ทั่วไป: ดึงส่วนไหนออกมาก็ได้”infer จับตำแหน่งไหนใน structure ก็ได้ — element type, promise value, argument type:
// element type of an arraytype ElementType<T> = T extends (infer E)[] ? E : T;type E1 = ElementType<number[]>; // numbertype E2 = ElementType<string>; // string (unchanged — not an array)
// the value a Promise resolves totype Awaited1<T> = T extends Promise<infer V> ? V : T;type V1 = Awaited1<Promise<string>>; // string
// the first parameter typetype 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 ก็พอแล้ว