สร้าง RequireAtLeastOne เพื่อกำหนดให้ต้องมีพร็อพเพอร์ตี้อย่างน้อยหนึ่งตัวเสมอ
Nuttavut Thongjor
TypeScript

เรียนรู้การใช้ภาษา TypeScript เพื่อสร้างชนิดข้อมูล RequireAtLeastOne เพื่อกำหนดให้มีพร็อพเพอร์ตี้อย่างน้อยหนึ่งตัว

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

สมมติเรามีชนิดข้อมูล Contact สำหรับใช้กำหนดช่องทางการติดต่อประกอบด้วย เบอร์โทรศัพท์ (tel) เฟสบุ๊ค (facebook) และ ไลน์ (line) ดังนี้

TypeScript
1type Contact = {
2 tel: string;
3 facebook: string;
4 line: string;
5};

ถ้าเราต้องการสร้างตัวแปรชื่อ myContact โดยกำหนดว่าอย่างน้อยที่สุดต้องมีหนึ่งช่องทางการติดต่อเสมอ นั่นแปลว่าตัวแปร contact อย่างน้อยที่สุดต้องมี tel, facebook หรือ line อย่างน้อยหนึ่งตัว

TypeScript
1// error เพราะต้องประกอบด้วย tel, facebook หรือ line อย่างน้อยหนึ่งตัว
2const myContact: Contact = {};
3
4// ไม่ error
5const myContact: Contact = { tel: '081-111-1111' };

เพื่อให้เป็นไปตามข้อกำหนดเราต้องแปลงรูปร่างของชนิดข้อมูล Contact ของเราเสียใหม่ โดยการใช้ Union เพื่อรวมชุดข้อมูล 3 ประเภทเข้าด้วยกัน

  • ชุดแรกระบุให้ต้องมี tel เสมอ ค่าอื่นจะมีหรือไม่ก็ได้
  • ชุดสองระบุให้ต้องมี facebook เสมอ ค่าอื่นจะมีหรือไม่ก็ได้
  • ชุดสุดท้ายระบุให้ต้องมี line เสมอ ค่าอื่นจะมีหรือไม่ก็ได้

เมื่อรวมชุดข้อมูลทั้งสามเข้าด้วยกันผ่าน Union จะได้ผลลัพธ์สุดท้ายเป็นดังนี้

TypeScript
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 เดิมแต่ได้ใจความเหมือนการแบ่งแยกชุดข้อมูลสามชุดข้างต้น

TypeScript
1type Contact = RequireAtLeastOne<{
2 tel: string;
3 facebook: string;
4 line: string;
5}>;

จากข้อมูลตั้งต้นจะสังเกตได้ว่าสาเหตุที่เราต้องสร้างชุดข้อมูลสามชุดมา Union กันนั่นเพราะ Contact มีสามพร็อพเพอร์ตี้ นั่นแสดงว่าการสร้าง RequireAtLeastOne ต้องอาศัยการวนลูป

TypeScript
1type RequireAtLeastOne<T> = {
2 [K in keyof T]-?: K;
3};

เมื่อกำหนดให้ T เป็น Contact ผลลัพธ์จากโค้ดข้างต้นได้ผลลัพธ์เช่นเดียวกับโค้ดต่อไปนี้

TypeScript
1type RequireAtLeastOne =
2 | { tel: 'tel' }
3 | { facebook: 'facebook' }
4 | { line: 'line' };

แต่สิ่งที่เราต้องการแท้จริงแล้วคือการให้แต่ละ key ประกอบไปด้วยออบเจ็กต์ที่ระบุให้ key นั้น ๆ มีค่าเสมอ ส่วน key อื่นจะมีหรือไม่มีก็ได้

TypeScript
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 };

ผลลัพธ์ที่เราอยากได้นี้ต้องอาศัยการแปลงเป็นชนิดข้อมูลดังนี้

TypeScript
1type RequireAtLeastOne<T> = {
2 [K in keyof T]-?: Required<Pick<T, K>> & Partial<Omit<T, K>>;
3};

สุดท้ายเราจะกำจัดค่าของ key ทั้งสามออกไปเพื่อให้เหลือแต่ค่าของชนิดข้อมูลทั้งสามนำมา Union กัน เราจึงใช้ Indexed access types เข้าช่วย ดังนี้

TypeScript
1type RequireAtLeastOne<T> = {
2 [K in keyof T]-?: Required<Pick<T, K>> & Partial<Omit<T, K>>;
3}[keyof T];

โค้ดอย่างสมบูรณ์สำหรับการนิยามและใช้งาน RequireAtLeastOne เป็นดังนี้

TypeScript
1type RequireAtLeastOne<T> = {
2 [K in keyof T]-?: Required<Pick<T, K>> & Partial<Omit<T, K>>;
3}[keyof T];
4
5type Contact = RequireAtLeastOne<{
6 tel: string;
7 facebook: string;
8 line: string;
9}>;
10
11// error
12const myContact1: Contact = {};
13
14// ไม่ error
15const myContact2: Contact = { tel: '081-111-1111' };

คอร์สออนไลน์ Comprehensive TypeScript คอร์สสอนการใช้งาน TypeScript ตั้งแต่เริ่มต้นจนถึงขั้นสูง เรียนรู้หลักการทำงานของ TypeScript การประกาศชนิดข้อมูลต่าง ๆ พร้อมการใช้งานขั้นสูงพร้อมรองรับการทำงานกับ TypeScript เวอร์ชัน 4.6 ด้วย