การใช้ชนิดข้อมูล Range เพื่อลดการประกาศชื่อพร็อพเพอร์ตี้เป็นตัวเลขต่อเนื่องใน TypeScript
Nuttavut Thongjor
TypeScript

เรียนรู้การสร้างชนิดข้อมูล Range และการประยุกต์ใช้งาน

คำอธิบาย
ความคิดเห็น

สมมติเราต้องการสร้างชนิดข้อมูล Config ใน TypeScript โดยกำหนดค่าพร็อพเพอร์ตี้ให้เป็นชื่อขึ้นต้นด้วย Num ต่อเนื่องจาก Num1 ถึง Num10 ดังนี้

TypeScript
1type Config = {
2 Num1: number
3 Num2: number
4 Num3: number
5 Num4: number
6 Num5: number
7 Num6: number
8 Num7: number
9 Num8: number
10 Num9: number
11 Num10: number
12}

แค่ Num1 ถึง Num10 อาจยังไม่เดือดร้อนที่จะเขียนมันขึ้นมา แต่จินตนาการถึงเมื่อต้องสร้าง Num1 ถึง Num30 ดู เลขเยอะขนาดนี้คงไม่มีใครอยากอ่านโค้ดแล้วหละ

เราสามารถสร้างชนิดข้อมูล Range มาแก้ไขปัญหานี้ได้ โดย Range นั้นรับ Generic Parameters สองค่า ค่าแรกคือจุดเริ่มต้น ค่าถัดมาคือจุดสิ้นสุด (ผลลัพธ์ไม่รวมค่านี้) การใช้งาน Range จะคืนชนิดข้อมูลที่เป็นการรวม (Union Types) ระหว่างเหล่าตัวเลขที่เริ่มต้นด้วยค่าแรกที่ส่งมาและสิ้นสุดที่ค่าก่อนค่าสุดท้ายที่ส่งเข้ามา ตัวอย่างการเรียกใช้งาน เช่น

TypeScript
1// มีค่าเท่ากับ 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
2Range<1, 10>
3
4// มีค่าเท่ากับ 35 | 36 | 37 | 38 | 39 | 40
5Range<35, 41>

เราสามารถนำค่าจาก Range มาใช้กับ Key Remapping ใน Map Types เพื่อสร้างชื่อของพร็อพเพอร์ตี้ตามเงื่อนไขข้างต้นได้ ดังนี้

TypeScript
1type Config = {
2 [K in Range<1, 11> as `Num${K}`]: number;
3};

ผลลัพธ์ที่ได้จะเหมือนกับการประกาศชนิดข้อมูล Config ในต้นบทความ

สำหรับวิธีการสร้างชนิดข้อมูล Range นั้นอาศัยโค้ดดังต่อไปนี้

TypeScript
1export type PrependNumber<A extends unknown[]> = A['length'] extends infer L
2 ? ((length: L, ...args: A) => void) extends (...args: infer R) => void
3 ? R
4 : never
5 : never
6
7export type EnumerateTo<N extends number, A extends unknown[] = []> = {
8 done: A extends (infer R)[] ? R : never
9 build: EnumerateTo<N, PrependNumber<A>>
10}[A['length'] extends N ? 'done' : 'build']
11
12export type Range<From extends number, To extends number> = Exclude<
13 EnumerateTo<To>,
14 EnumerateTo<From>
15>

ในส่วนนี้จะละการอธิบายวิธีการสร้าง Range เนื่องจากผู้เขียนจะทำการแยกส่วนอธิบายนี้ไว้เป็นอีกบทความนึง