เรียนรู้การใช้ภาษา TypeScript เพื่อสร้างชนิดข้อมูล RequireAtLeastOne เพื่อกำหนดให้มีพร็อพเพอร์ตี้อย่างน้อยหนึ่งตัว
สมมติเรามีชนิดข้อมูล Contact สำหรับใช้กำหนดช่องทางการติดต่อประกอบด้วย เบอร์โทรศัพท์ (tel) เฟสบุ๊ค (facebook) และ ไลน์ (line) ดังนี้
1type Contact = {2 tel: string;3 facebook: string;4 line: string;5};
ถ้าเราต้องการสร้างตัวแปรชื่อ myContact โดยกำหนดว่าอย่างน้อยที่สุดต้องมีหนึ่งช่องทางการติดต่อเสมอ นั่นแปลว่าตัวแปร contact อย่างน้อยที่สุดต้องมี tel, facebook หรือ line อย่างน้อยหนึ่งตัว
1// error เพราะต้องประกอบด้วย tel, facebook หรือ line อย่างน้อยหนึ่งตัว2const myContact: Contact = {};34// ไม่ error5const myContact: Contact = { tel: '081-111-1111' };
เพื่อให้เป็นไปตามข้อกำหนดเราต้องแปลงรูปร่างของชนิดข้อมูล Contact ของเราเสียใหม่ โดยการใช้ Union เพื่อรวมชุดข้อมูล 3 ประเภทเข้าด้วยกัน
- ชุดแรกระบุให้ต้องมี tel เสมอ ค่าอื่นจะมีหรือไม่ก็ได้
- ชุดสองระบุให้ต้องมี facebook เสมอ ค่าอื่นจะมีหรือไม่ก็ได้
- ชุดสุดท้ายระบุให้ต้องมี line เสมอ ค่าอื่นจะมีหรือไม่ก็ได้
เมื่อรวมชุดข้อมูลทั้งสามเข้าด้วยกันผ่าน Union จะได้ผลลัพธ์สุดท้ายเป็นดังนี้
1type Contact =2 | {3 tel: string;4 facebook?: string;5 line?: string;6 }7 | {8 tel?: string;9 facebook: string;10 line?: string;11 }12 | {13 tel?: string;14 facebook?: string;15 line: string;16 };
จะเห็นว่าการนิยามรูปแบบใหม่ด้วยการแบ่งเป็นชุดข้อมูลย่อยค่อนข้างจะซับซ้อนและยากต่อการทำความเข้าใจ บทความนี้เราจึงสร้างชนิดข้อมูลใหม่ชื่อ RequireAtLeastOne เพื่อครอบทับ Contact เดิมแต่ได้ใจความเหมือนการแบ่งแยกชุดข้อมูลสามชุดข้างต้น
1type Contact = RequireAtLeastOne<{2 tel: string;3 facebook: string;4 line: string;5}>;
จากข้อมูลตั้งต้นจะสังเกตได้ว่าสาเหตุที่เราต้องสร้างชุดข้อมูลสามชุดมา Union กันนั่นเพราะ Contact มีสามพร็อพเพอร์ตี้ นั่นแสดงว่าการสร้าง RequireAtLeastOne ต้องอาศัยการวนลูป
1type RequireAtLeastOne<T> = {2 [K in keyof T]-?: K;3};
เมื่อกำหนดให้ T เป็น Contact ผลลัพธ์จากโค้ดข้างต้นได้ผลลัพธ์เช่นเดียวกับโค้ดต่อไปนี้
1type RequireAtLeastOne =2 | { tel: 'tel' }3 | { facebook: 'facebook' }4 | { line: 'line' };
แต่สิ่งที่เราต้องการแท้จริงแล้วคือการให้แต่ละ key ประกอบไปด้วยออบเจ็กต์ที่ระบุให้ key นั้น ๆ มีค่าเสมอ ส่วน key อื่นจะมีหรือไม่มีก็ได้
1type RequireAtLeastOne =2 | {3 tel: {4 tel: string;5 facebook?: string;6 line?: string;7 };8 }9 | {10 facebook: {11 tel?: string;12 facebook: string;13 line?: string;14 };15 }16 | {17 line: {18 tel?: string;19 facebook?: string;20 line: string;21 };22 };
ผลลัพธ์ที่เราอยากได้นี้ต้องอาศัยการแปลงเป็นชนิดข้อมูลดังนี้
1type RequireAtLeastOne<T> = {2 [K in keyof T]-?: Required<Pick<T, K>> & Partial<Omit<T, K>>;3};
สุดท้ายเราจะกำจัดค่าของ key ทั้งสามออกไปเพื่อให้เหลือแต่ค่าของชนิดข้อมูลทั้งสามนำมา Union กัน เราจึงใช้ Indexed access types เข้าช่วย ดังนี้
1type RequireAtLeastOne<T> = {2 [K in keyof T]-?: Required<Pick<T, K>> & Partial<Omit<T, K>>;3}[keyof T];
โค้ดอย่างสมบูรณ์สำหรับการนิยามและใช้งาน RequireAtLeastOne เป็นดังนี้
1type RequireAtLeastOne<T> = {2 [K in keyof T]-?: Required<Pick<T, K>> & Partial<Omit<T, K>>;3}[keyof T];45type Contact = RequireAtLeastOne<{6 tel: string;7 facebook: string;8 line: string;9}>;1011// error12const myContact1: Contact = {};1314// ไม่ error15const myContact2: Contact = { tel: '081-111-1111' };
คอร์สออนไลน์ Comprehensive TypeScript คอร์สสอนการใช้งาน TypeScript ตั้งแต่เริ่มต้นจนถึงขั้นสูง เรียนรู้หลักการทำงานของ TypeScript การประกาศชนิดข้อมูลต่าง ๆ พร้อมการใช้งานขั้นสูงพร้อมรองรับการทำงานกับ TypeScript เวอร์ชัน 4.6 ด้วย