Conditional Types
if-statement สำหรับ type
หัวข้อที่มีชื่อว่า “if-statement สำหรับ type”conditional type เลือกหนึ่งในสอง type ตามความสัมพันธ์ ใช้ syntax ternary ที่คุณรู้จักอยู่แล้ว — แต่ในระดับ type:
type IsString<T> = T extends string ? "yes" : "no";
type A = IsString<string>; // "yes"type B = IsString<number>; // "no"type C = IsString<"hi">; // "yes" ("hi" extends string)อ่าน T extends U ? X : Y ว่า “ถ้า T assignable ให้ U type จะเป็น X ไม่งั้นเป็น Y” extends ตรงนี้คือการทดสอบ subset แบบเดียวกับในโมดูล foundations — ไม่ใช่ inheritance
การซ้อน: decision tree เล็ก ๆ
หัวข้อที่มีชื่อว่า “การซ้อน: decision tree เล็ก ๆ”conditional ซ้อนกันได้เพื่อสร้างการแตกกิ่งหลายทาง:
type TypeName<T> = T extends string ? "string" : T extends number ? "number" : T extends boolean ? "boolean" : T extends undefined ? "undefined" : T extends Function ? "function" : "object";
type T1 = TypeName<string>; // "string"type T2 = TypeName<() => void>; // "function"type T3 = TypeName<number[]>; // "object"แต่ละ condition ถูกลองตามลำดับ เหมือน chain ของ else if
การ distribute ผ่าน union
หัวข้อที่มีชื่อว่า “การ distribute ผ่าน union”นี่คือพฤติกรรมที่ทำทุกคนงง เมื่อ type ที่ถูกตรวจเป็น naked type parameter และคุณส่ง union ให้ conditional จะ distribute ไปทีละ member แล้ว union ผลลัพธ์เข้าด้วยกัน:
type ToArray<T> = T extends any ? T[] : never;
type R = ToArray<string | number>;// ^? string[] | number[]ผลลัพธ์ ไม่ ใช่ (string | number)[] แต่ conditional รันแยกกันสำหรับ string (ได้ string[]) และ number (ได้ number[]) แล้ว union กัน นี่คือพฤติกรรม distributive conditional type
flowchart TB input["ToArray ของ string หรือ number"] --> split["distribute ไปทีละ member"] split --> s["กิ่ง string: string array"] split --> n["กิ่ง number: number array"] s --> out["union ผลลัพธ์: string array หรือ number array"] n --> out
การใช้จริงที่พบบ่อยคือการ filter union Exclude (built-in) คือสิ่งนี้เป๊ะ:
type MyExclude<T, U> = T extends U ? never : T;
type Colors = "red" | "green" | "blue";type NoRed = MyExclude<Colors, "red">;// ^? "green" | "blue"member ที่ match U จะกลายเป็น never (และ never หายไปจาก union) ดังนั้นเหลือแค่ member ที่ไม่ match
ปิด distribution
หัวข้อที่มีชื่อว่า “ปิด distribution”บางครั้งคุณอยากตรวจ union ทั้งก้อน ไม่ใช่ทีละ member ห่อทั้งสองฝั่งด้วย tuple หนึ่งช่องเพื่อหยุด distribution — type parameter จะไม่ “naked” อีกต่อไป:
type IsUnionOfStrings<T> = [T] extends [string] ? true : false;
type D1 = IsUnionOfStrings<"a" | "b">; // true (tested as a whole)
// compare with the distributive version:type Bad<T> = T extends string ? true : false;type D2 = Bad<"a" | number>; // boolean (true | false — distributed!)type D3 = IsUnionOfStrings<"a" | number>; // false (whole union isn't all string)ทริก [T] extends [U] เป็น idiom มาตรฐาน — หยิบมาใช้เมื่อ conditional ผ่าน union ให้ผลเป็น boolean แบบแปลก ๆ (สัญญาณว่า distribute แล้ว union true | false เข้าด้วยกัน)