ชนิดแบบเรียกซ้ำสำหรับระเบียนที่ซ้อนกันใน TypeScript
ตั้งแต่เวอร์ชัน 3.7 เป็นต้นมา TypeScript มีการรองรับชนิดข้อมูลแบบเรียกซ้ำ ซึ่งผมคิดว่ามันจะช่วยให้ผมสร้างชนิดข้อมูลที่เหมาะสมสำหรับโครงสร้างบันทึกซ้อนกันได้ แต่จนถึงตอนนี้ผมยังไม่สามารถทำให้คอมไพเลอร์พอใจกับความพยายามของผมได้
ลองลืมเรื่องเจนเนอริกและยกตัวอย่างอย่างน้อยที่สุดว่า ฉันมีวัตถุหนึ่ง ซึ่งมีค่าตัวเลขที่ความลึกใดก็ได้อยู่เบื้องหลังคีย์ที่เป็นสตริง
type NestedRecord = Record<string, number | NestedRecord>
const test: NestedRecord = {
a: 10,
b: {
c: 20,
d: 30,
}
}
การพิมพ์แบบนี้จะมีเหตุผลสำหรับฉัน แต่ว่าโปรแกรมคอมไพเลอร์ให้ข้อผิดพลาดดังนี้:
Type alias 'NestedRecord' อ้างอิงตัวเองเป็นวงจร
มีข้อจำกัดบางอย่างของการสนับสนุนประเภทแบบเรียกซ้ำที่ฉันไม่ทราบหรือไม่? ถ้าเป็นเช่นนั้น จะสามารถกำหนดประเภทสำหรับโครงสร้างนี้ได้อย่างไร?
แก้ไข: ตามคำตอบของ @jcalz ฉันจึงจบลงด้วยการใช้คำนิยามทั่วไปดังต่อไปนี้:
type NestedRecord<T> = { [key: string]: NestedRecordField<T> }
type NestedRecordField<T> = T | NestedRecord<T>
ในกรณีของฉัน การแยกสิ่งเหล่านี้ออกมาดูสมเหตุสมผล เนื่องจากฟังก์ชันแบบเรียกซ้ำที่ทำงานกับบันทึกดังกล่าวต้องการประเภท NestedRecordField อยู่แล้ว แต่โซลูชันบรรทัดเดียวต่อไปนี้ก็สามารถใช้ได้เช่นกัน:
type NestedRecord<T> = { [key: string]: T | NestedRecord<T> }
คำตอบ #1
ดู microsoft/TypeScript#41164 สำหรับคำตอบมาตรฐานของคำถามนี้
TypeScript อนุญาตให้มี คำจำกัดความแบบวงกลมบางประเภท แต่บางประเภทถูกห้าม ตัวอย่างเช่น คุณไม่สามารถมี type Oops=Oops ได้ หากคุณมีประเภท generic เช่น type Foo<T>=... จะไม่ชัดเจนว่า type Bar=Foo<Bar> จะถูกอนุญาตหรือถูกห้าม เว้นแต่คอมไพเลอร์เต็มใจที่จะทำการแทนที่ Foo ด้วยคำจำกัดความของมันอย่างรวดเร็ว และคอมไพเลอร์ไม่ได้ทำเช่นนี้; มัน เลื่อนออกไป การประเมินดังกล่าว (เพื่อไม่ให้ประสิทธิภาพของคอมไพเลอร์เสื่อมลงอย่างมาก) ดังนั้นคอมไพเลอร์จะปฏิเสธ type Bar=Foo<Bar> แม้ว่าพบว่าคำจำกัดความของ Foo จะไม่เป็นอันตราย นี่คือข้อจำกัดด้านการออกแบบของ TypeScript.
ดังนั้นถึงแม้ว่า utility type Record<K, V> จะปลอดภัยสำหรับประเภทแบบ recursive ในพารามิเตอร์ V ตัวคอมไพเลอร์ก็ยังตรวจไม่เห็นเรื่องนั้น และคุณจะได้รับข้อผิดพลาด
วิธีที่แนะนำที่นี่คือการแทนที่ Record<K, V> ด้วยคำนิยามของมัน {[P in K]: V} สำหรับ Record<string, V> จะมีลักษณะเป็น {[P in string]: V} ซึ่งจะประเมินเป็นประเภทที่มีลายเซ็นดัชนีสตริง {[k: string]: V} ดังนั้นคุณจึงสามารถใช้มันโดยตรงได้:
type NestedRecord =
{ [k: string]: number | NestedRecord }; // okay
แล้วการมอบหมายงานของคุณก็ทำงานตามที่คาดไว้:
const test: NestedRecord = {
a: 10,
b: {
c: 20,
d: 30,
}
}
ลิงก์ Playground ไปยังโค้ด
