เจเนอริกใน TypeScript

เจเนอริกใน TypeScript

บทความนี้อธิบายเกี่ยวกับเจเนอริกใน TypeScript

YouTube Video

เจเนอริกใน TypeScript

เจเนอริกใน TypeScript เป็นฟีเจอร์ที่ช่วยให้คุณกำหนดฟังก์ชัน คลาส และอินเทอร์เฟซที่นำกลับมาใช้ซ้ำได้และปลอดภัยด้วยการพารามิเตอร์ไทป์ การใช้เจเนอริกช่วยให้คุณเขียนโค้ดที่ไม่ได้ขึ้นอยู่กับประเภทที่เฉพาะเจาะจง ทำให้คุณสามารถดำเนินการแบบเดียวกันกับประเภทที่หลากหลาย

พื้นฐานของเจเนอริก

เจเนอริกทำหน้าที่เหมือนเท็มเพลตที่รับประเภทเป็นอาร์กิวเมนต์ ทำให้ฟังก์ชันและคลาสสามารถจัดการกับหลายประเภทได้

ฟังก์ชันเจเนอริก

ตัวอย่างต่อไปนี้เป็นฟังก์ชันที่ระบุประเภทของอาร์กิวเมนต์โดยใช้เจเนอริก

1function identity<T>(value: T): T {
2    return value;
3}
4
5console.log(identity<number>(42));       // 42
6console.log(identity<string>("Hello"));  // Hello
  • T เป็นอาร์กิวเมนต์ประเภทเจเนอริกที่แทนประเภทของอาร์กิวเมนต์และค่าที่ส่งคืนของฟังก์ชัน ประเภทที่แท้จริงจะถูกกำหนดเมื่อฟังก์ชันถูกเรียกใช้
  • ด้วยการระบุ <number> หรือ <string> อย่างชัดเจน คุณกำลังระบุประเภท

ประเภทเจเนอริกทำงานได้โดยไม่ต้องระบุอย่างชัดเจน เนื่องจาก TypeScript จะทำการสรุปประเภทให้

1function identity<T>(value: T): T {
2    return value;
3}
4
5console.log(identity(42));       // 42
6console.log(identity("Hello"));  // Hello

ข้อจำกัดของเจเนอริก

โดยการกำหนดข้อจำกัดให้เจเนอริก คุณสามารถจำกัดให้ยอมรับเฉพาะประเภทที่กำหนดได้เท่านั้น

1function loggingIdentity<T extends { length: number }>(arg: T): T {
2    console.log(arg.length);
3    return arg;
4}
5
6loggingIdentity("Hello");  // 5
7loggingIdentity([1, 2, 3]);  // 3
8
9// loggingIdentity(42);  // Error: number does not have a length property.
  • การระบุ T extends { length: number } หมายความว่า T ต้องเป็นประเภทที่มีคุณสมบัติ length ดังนั้นประเภทที่ไม่มีคุณสมบัติ length จะไม่ได้รับการยอมรับ

คลาสเจเนอริก

คลาสยังสามารถถูกกำหนดโดยใช้เจเนอริกได้ คลาสเจเนอริกมีความยืดหยุ่นในเรื่องของประเภทสำหรับคุณสมบัติและเมธอด

 1class Box<T> {
 2    private _value: T;
 3
 4    constructor(value: T) {
 5        this._value = value;
 6    }
 7
 8    public getValue(): T {
 9        return this._value;
10    }
11
12    public setValue(value: T): void {
13        this._value = value;
14    }
15}
16
17const numberBox = new Box<number>(100);
18console.log(numberBox.getValue());  // 100
19
20const stringBox = new Box<string>("Hello");
21console.log(stringBox.getValue());  // Hello
  • Box<T> ประกาศประเภท T ที่ใช้ภายในคลาสเป็นเจเนอริก ช่วยให้คลาสเดียวกันสามารถนำกลับมาใช้กับประเภทที่แตกต่างกันได้

อินเทอร์เฟซเจเนอริก

เจเนอริกสามารถถูกใช้กับอินเทอร์เฟซได้เช่นกัน

 1interface Pair<T, U> {
 2    first: T;
 3    second: U;
 4}
 5
 6const numberStringPair: Pair<number, string> = { first: 1, second: "One" };
 7console.log(numberStringPair);  // { first: 1, second: 'One' }
 8
 9const booleanArrayPair: Pair<boolean, number[]> = { first: true, second: [1, 2, 3] };
10console.log(booleanArrayPair);  // { first: true, second: [ 1, 2, 3 ] }
  • โดยการระบุสองประเภทเจเนอริกด้วย Pair<T, U> คุณสามารถกำหนดวัตถุที่ผสมประเภทที่แตกต่างกันได้

อาร์กิวเมนต์ประเภทเริ่มต้น

นอกจากนี้ยังสามารถระบุประเภทเริ่มต้นสำหรับอาร์กิวเมนต์ประเภททั่วไปได้

1function createArray<T = string>(length: number, value: T): T[] {
2    return Array(length).fill(value);
3}
4
5console.log(createArray(3, "a"));   // ['a', 'a', 'a']
6console.log(createArray(3, 100));   // [100, 100, 100]
  • เรากำหนดอาร์กิวเมนต์ประเภทเริ่มต้นเป็น string ด้วย <T = string> ถ้าไม่มีการระบุประเภทอย่างชัดเจน T จะมีประเภทเป็น string

ชื่อเรียกประเภททั่วไป

ประเภททั่วไปสามารถใช้เป็นชื่อเรียกประเภท (type) ได้เช่นกัน

 1type Result<T> = {
 2    success: boolean;
 3    data: T;
 4};
 5
 6const successResult: Result<number> = { success: true, data: 42 };
 7const errorResult: Result<string> = { success: false, data: "Error occurred" };
 8
 9console.log(successResult);  // { success: true, data: 42 }
10console.log(errorResult);    // { success: false, data: 'Error occurred' }
  • Result<T> แทนวัตถุผลลัพธ์ที่มีข้อมูลของประเภท T ด้วยวิธีนี้ คุณสามารถสร้างชื่อเรียกประเภทที่ยืดหยุ่นได้โดยใช้ประเภททั่วไป

ประเภททั่วไปหลายตัว

ด้วยการใช้ประเภททั่วไปหลายตัว คุณสามารถกำหนดฟังก์ชันและคลาสที่หลากหลายยิ่งขึ้น

1function merge<T, U>(obj1: T, obj2: U): T & U {
2    return { ...obj1, ...obj2 };
3}
4
5const person = { name: "Alice" };
6const job = { title: "Engineer" };
7
8const merged = merge(person, job);
9console.log(merged);  // { name: 'Alice', title: 'Engineer' }
  • ฟังก์ชัน merge รับประเภทสองประเภทที่แตกต่างกัน T และ U และรวมกันเพื่อนำกลับมาเป็นวัตถุใหม่

สรุป

  • Generics ช่วยให้สร้างโค้ดที่นำกลับมาใช้ใหม่ได้และปลอดภัยในเรื่องประเภท โดยการพิจารณาประเภทเป็นพารามิเตอร์
  • ด้วยการใช้ประเภททั่วไปใน ฟังก์ชัน คลาส และอินเตอร์เฟซ คุณสามารถเขียนตรรกะที่ยืดหยุ่นรองรับประเภทต่าง ๆ ได้
  • ด้วยการเพิ่ม ข้อจำกัดไปยังอาร์กิวเมนต์ประเภท หรือกำหนด อาร์กิวเมนต์ประเภทเริ่มต้น คุณสามารถควบคุมขอบเขตของประเภททั่วไปได้

ด้วยการใช้ประเภททั่วไป คุณสามารถเขียนโค้ดที่ไม่ขึ้นกับประเภท มีวัตถุประสงค์ทั่วไป และใช้ประโยชน์จากระบบประเภทที่ทรงพลังของ TypeScript ได้อย่างสูงสุด

คุณสามารถติดตามบทความข้างต้นโดยใช้ Visual Studio Code บนช่อง YouTube ของเรา กรุณาตรวจสอบช่อง YouTube ด้วย

YouTube Video