ตรวจสอบประเภทต่าง ๆ ในอาเรย์เดียวกันใน TypeScript
ควรนิยาม MyType อย่างไรเพื่อให้แน่ใจว่าแต่ละรายการในอาร์เรย์ของฉันเป็นอาร์เรย์ที่ประกอบด้วยรายการที่มีชนิดเดียวกัน?
const array1: MyType = [["foo", "bar"], [42, 42], [true, false]]; // OK
const array2: MyType = [["foo", "bar"], [42, 42], [true, "false"]]; // Should throw a TS error
ในตัวอย่างของฉันฉันใช้ string, number และ boolean แต่สามารถเป็นอะไรก็ได้
คำตอบ #1
ไม่มีชนิดเฉพาะใน TypeScript ที่ทำงานในลักษณะนี้ โดยแนวคิดคือคุณต้องการมี ชนิดทั่วไปที่มีการกำหนดเชิงมีอยู่ เช่น
// don't write this, it doesn't work
type MyType = Array< <exists T>Array<T> >
เพื่อกล่าวว่า MyType เป็นอาร์เรย์ของอาร์เรย์ ซึ่งแต่ละสมาชิกเป็นของประเภทอาร์เรย์ บางประเภท ก็ใช่ว่าจะใช้ได้เช่นกัน เนื่องจาก [true, "false"] เป็นของประเภทอาร์เรย์ Array<true | "false"> ดังนั้นอาจต้องพยายามชี้แนวทางการอนุมานให้ออกจากสมาชิกของแต่ละอาร์เรย์หลังตัวแรก โดยอาจใช้ประเภทยูทิลิตี NoInfer เช่น
// don't write this, it doesn't work
type MyType = Array< <exists T>[T, ...NoInfer<T>[]] >
แต่ไม่ว่าจะอย่างไรก็ใช้ไม่ได้ TypeScript ไม่สนับสนุนประเภทอัตวิสัยโดยตรง และคุณไม่สามารถพูดว่า "บางประเภท" ได้ คุณสามารถพูดได้แค่ "สำหรับทุกประเภท" โดยใช้ generic ที่มีการกำหนดค่าแบบสากล ซึ่งนั่นคือสิ่งที่ TypeScript (และภาษาอื่นๆ ส่วนใหญ่ที่มี generics) สนับสนุน
นั่นหมายความว่าคุณต้องทำให้ MyType เป็นแบบทั่วไป (generic) ดังนี้:
type MyType<T extends unknown[]> = { [I in keyof T]: readonly [T[I], ...NoInfer<T[I]>[]] }
ดังนั้นตอนนี้คุณสามารถเขียนได้
const array1: MyType<[string, number, boolean]> =
[["foo", "bar"], [42, 42], [true, false, false]]; // okay
const array2: MyType<[string, number, boolean]> =
[["foo", "bar"], [42, 42], [true, "false"]]; // error
แต่นั่นมันซ้ำซ้อน คุณถูกบังคับให้เขียน [string, number, boolean] มันคงจะดีถ้า TypeScript สามารถ infer สิ่งนั้นให้คุณได้ น่าเสียดายที่นี่ไม่ใช่วิธีการทำงานของ generic types มีคำขอฟีเจอร์ที่ microsoft/TypeScript#32794 ซึ่งถ้าได้รับการนำไปใช้ อาจหมายความว่าคุณสามารถเขียน const array1: MyType<infer>=⋯ ได้ แต่ตอนนี้มันยังไม่เป็นส่วนหนึ่งของภาษานะ ดังนั้นคุณจึงต้องหาวิธีแก้ไขเรื่องนี้ด้วย
TypeScript จะสรุปประเภทอาร์กิวเมนต์แบบทั่วไปเฉพาะเมื่อคุณเรียกใช้ ฟังก์ชัน แบบทั่วไป ดังนั้นเราสามารถเขียนฟังก์ชันช่วยที่เพียงแค่ส่งค่าของมันกลับในเวลารันไทม์ แต่ให้การสรุปประเภทที่คุณต้องการ เช่นนี้:
const myType = <T extends unknown[]>(t: MyType<T>) => t;
const array1 = myType([["foo", "bar"], [42, 42], [true, false, false]]); // okay
const array2 = myType([["foo", "bar"], [42, 42], [true, "false"]]); // error!
ดูดี
สังเกตว่าประเภทของ array1 และ array2 ยังคงเหมือนเดิม แต่ตอนนี้คุณไม่จำเป็นต้องกำกับประเภท นอกจากนี้สังเกตว่า const arr=myType([⋯]) ไม่ยากในการเขียนมากไปกว่า const arr: MyType=[⋯]
ลิงก์ Playground ไปยังโค้ด
