Programming
น้องๆ เคยเจอไหม เขียน Typescript ไปสักพัก โค้ดเริ่มซับซ้อน Type มันเริ่มงงๆ แก้ตรงนี้ พังตรงนั้น นั่นแหละถึงเวลาที่เราต้องงัดเอา Advanced Patterns ออกมาใช้แล้ว
Advanced Patterns ใน Typescript เนี่ย มันคือเทคนิคการเขียน Type ที่ซับซ้อนขึ้น เพื่อจัดการกับความยุ่งเหยิงของ Type ให้มันง่ายขึ้น อ่านง่ายขึ้น และที่สำคัญคือ ป้องกัน Error ที่อาจจะเกิดขึ้นตอน Runtime ได้ดีขึ้นเยอะเลยนะ
สมัยผมทำร้านเน็ตฯ เนี่ย โค้ดมันก็ไม่ได้ซับซ้อนขนาดนี้หรอก แต่ยุคนี้ Software มันใหญ่ขึ้นเยอะ Advanced Patterns เลยสำคัญมากๆ ถ้าเราอยากเขียนโค้ดที่มัน Scale ได้ดี และ Maintainable ในระยะยาว
ก่อนจะไป Advanced เราต้องแน่นพื้นฐานก่อนนะ เหมือนตอนหัดเล่นเกมส์ ก็ต้องรู้ปุ่ม เดิน กระโดด ต่อย ก่อนถึงจะไปคอมโบได้
Generics คือตัวแปร Type ที่เราสามารถกำหนดตอนใช้งานได้ ทำให้เราเขียน Function หรือ Class ที่ทำงานกับ Type ที่หลากหลายได้ โดยไม่ต้องเขียน Function หรือ Class ซ้ำๆ
function identity<T>(arg: T): T {
return arg;
}
let myString: string = identity<string>("hello");
let myNumber: number = identity<number>(123);
Generics เนี่ย เหมือนเรามี Template ที่ใส่ Type อะไรก็ได้เข้าไป ตอนผมเขียนโปรแกรมคิดเงินในร้านเน็ตฯ สมัยก่อน ถ้ามี Generics คงไม่ต้องเขียน Function ซ้ำๆ เยอะขนาดนั้น
Type Aliases คือการตั้งชื่อเล่นให้ Type ส่วน Interface คือการกำหนดโครงสร้างของ Object ว่าต้องมี Property อะไรบ้าง
type StringOrNumber = string | number;
interface Person {
name: string;
age: number;
}
Type Aliases กับ Interface เนี่ย ช่วยให้โค้ดเราอ่านง่ายขึ้นเยอะเลย เหมือนเราตั้งชื่อไฟล์ให้มันสื่อความหมาย แทนที่จะตั้งชื่อแบบมั่วๆ
การใช้งาน Advanced Patterns ใน Typescript เนี่ย ไม่ได้ยากอย่างที่คิด เริ่มจากทำความเข้าใจ Concept ก่อน แล้วค่อยๆ ลองเอาไปปรับใช้กับ Project ของเรา
อย่ากลัวที่จะลองผิดลองถูกนะ สมัยผมหัดเขียนโปรแกรมใหม่ๆ ก็เจ๊งไปเยอะ กว่าจะคลำทางถูก
มาดูขั้นตอนปฏิบัติจริงกันบ้าง ว่าเราจะเริ่มใช้ Advanced Patterns ยังไง
Conditional Types คือ Type ที่จะเปลี่ยนไปตามเงื่อนไขที่กำหนด คล้ายๆ กับ Ternary Operator ใน Javascript
type IsString<T> = T extends string ? true : false;
type StringCheck = IsString<string>; // true
type NumberCheck = IsString<number>; // false
Conditional Types เนี่ย ช่วยให้เราสร้าง Type ที่มัน Dynamic ได้ ทำให้โค้ดเรา Flexible มากขึ้น
Mapped Types คือ Type ที่สร้างจาก Type อื่น โดยการแปลง Property ของ Type เดิม
interface Person {
name: string;
age: number;
}
type ReadonlyPerson = {
readonly [K in keyof Person]: Person[K];
};
Mapped Types เนี่ย ช่วยให้เราไม่ต้องเขียน Type ซ้ำๆ ถ้าเราต้องการสร้าง Type ที่คล้ายกับ Type เดิม แต่มี Property บางอย่างที่แตกต่างกัน
Typescript มี Utility Types มาให้เราใช้เยอะแยะ เช่น Partial, Required, Readonly, Pick, Omit แต่ละตัวก็มีประโยชน์แตกต่างกันไป ลองศึกษาดูนะ
interface Person {
name: string;
age?: number;
}
type PartialPerson = Partial<Person>; // All properties are optional
type RequiredPerson = Required<Person>; // All properties are required
Utility Types เนี่ย ช่วยลดเวลาในการเขียนโค้ดได้เยอะเลย เหมือนเรามี Library สำเร็จรูปให้ใช้
ลองเข้าไปดู SiamCafe Blog อาจจะมีบทความที่อธิบาย Utility Types เพิ่มเติมนะ
แน่นอนว่า Typescript ไม่ใช่ทางเลือกเดียวในการจัดการ Type ใน Javascript แต่ Typescript เป็นทางเลือกที่ได้รับความนิยมมากที่สุด
สมัยก่อนตอนที่ยังไม่มี Typescript ผมก็เขียน Javascript แบบดิบๆ เลย Error เพียบ Debug กันหัวแตก
| Feature | Typescript | Flow | Plain Javascript |
|---|---|---|---|
| Type Checking | Static | Static | Dynamic |
| Community Support | Large | Moderate | N/A |
| Learning Curve | Moderate | Moderate | Low |
| Integration | Excellent | Good | N/A |
จากตารางจะเห็นว่า Typescript มีข้อดีหลายอย่าง แต่ก็มี Learning Curve ที่สูงกว่า Javascript ธรรมดา
Flow เป็นอีกทางเลือกหนึ่งที่คล้ายกับ Typescript แต่ Community Support ไม่ใหญ่เท่า
สุดท้ายแล้ว การเลือกใช้ Tools อะไร ก็ขึ้นอยู่กับ Project ของเรา และความถนัดของแต่ละคน
ลองเข้าไปอ่านบทความอื่นๆ ใน SiamCafe Blog ดูนะ อาจจะมี Tools ที่เหมาะกับ Project ของน้องๆ ก็ได้
ดูวิดีโอเพิ่มเติมเกี่ยวกับTypescript Advanced Patterns:
น้องๆ หลายคนถามพี่มาเยอะว่า Typescript เนี่ยมัน Advanced ตรงไหน แล้วทำไมต้องใช้ Patterns ให้มันวุ่นวาย พี่บอกเลยว่าหัวใจสำคัญคือเรื่อง "ความยืดหยุ่น" และ "ความปลอดภัย" ของโค้ดเรานี่แหละ
สมัยพี่ทำร้านเน็ต (SiamCafe ยุคบุกเบิก) โค้ดมันไม่ได้ซับซ้อนขนาดนี้หรอก แต่พอมาเขียนโปรแกรมใหญ่ๆ ที่มีคนหลายคนช่วยกันทำเนี่ย ถ้าโค้ดไม่ดี ไม่คลีน รับรองว่านรกแตกแน่นอน แก้บั๊กกันทั้งวันทั้งคืน
Utility Types ใน Typescript เนี่ย เหมือนเป็นเครื่องมือสารพัดประโยชน์ที่เราเอามาปรับแต่ง Types ได้ตามใจชอบเลยนะ พี่ชอบใช้ Partial กับ Readonly บ่อยมาก
สมมติว่าเรามี Interface User แบบนี้
interface User {
id: number;
name: string;
email: string;
}
ถ้าเราอยากจะสร้าง Function ที่ Update User บาง Field ได้ เราสามารถใช้ Partial ได้เลย
function updateUser(id: number, updates: Partial<User>) {
// ... logic update user ...
}
updateUser(1, { name: "John Doe" }); // Update แค่ชื่อ
เห็นมั้ยว่าเราไม่ต้องสร้าง Interface ใหม่ให้วุ่นวาย
Discriminated Unions เนี่ย ช่วยให้เราจัดการกับ Types ที่มีความแตกต่างกันได้ง่ายขึ้นเยอะมาก พี่เคยเจอเคสที่ต้องจัดการกับ API Response หลายรูปแบบ ซึ่งแต่ละรูปแบบก็มี Field ที่ต่างกัน Discriminated Unions ช่วยให้ชีวิตพี่ง่ายขึ้นเยอะเลย
ลองดูตัวอย่างนี้
type SuccessResponse = {
status: "success";
data: any;
};
type ErrorResponse = {
status: "error";
message: string;
};
type APIResponse = SuccessResponse | ErrorResponse;
function handleResponse(response: APIResponse) {
if (response.status === "success") {
console.log("Data:", response.data);
} else {
console.error("Error:", response.message);
}
}
Typescript จะรู้ได้เลยว่าถ้า response.status เป็น "success" เราสามารถเข้าถึง response.data ได้อย่างปลอดภัย
Conditional Types เนี่ย เหมือนเป็น If-Else Statement ในโลกของ Types เลยนะ มันช่วยให้เราสร้าง Types ที่มีความซับซ้อนและยืดหยุ่นได้มาก พี่เคยใช้ Conditional Types ในการสร้าง Type ที่จะ Return Type ที่ถูกต้องตามเงื่อนไขบางอย่าง
type StringOrNumber<T> = T extends string ? string : number;
type Result1 = StringOrNumber<string>; // string
type Result2 = StringOrNumber<number>; // number
เห็นมั้ยว่า Type StringOrNumber จะ Return string ถ้า Type T เป็น string และ Return number ถ้า Type T เป็น number
Mapped Types คือการที่เราสามารถแปลง Type ที่มีอยู่แล้วให้เป็น Type ใหม่ได้ง่ายๆ สมัยก่อนตอนพี่เขียน Javascript เพียวๆ กว่าจะแก้ Data Structure ได้แต่ละทีนี่แทบกระอัก แต่พอมาใช้ Typescript ชีวิตมันง่ายขึ้นเยอะเลยจริงๆ
interface Product {
name: string;
price: number;
quantity: number;
}
type ReadonlyProduct = Readonly<Product>;
const product: ReadonlyProduct = {
name: "Laptop",
price: 1200,
quantity: 5
};
// product.price = 1500; // Error: Cannot assign to 'price' because it is a read-only property.
จาก Code ตัวอย่าง เราสามารถทำให้ Properties ทั้งหมดใน Interface Product เป็น Readonly ได้ง่ายๆ โดยใช้ Readonly<Product>
พี่ว่ามันไม่ได้ยากเกินไปหรอกน้อง ถ้าเราเข้าใจ Concept พื้นฐานของ Typescript ดีแล้ว การใช้ Advanced Patterns จะช่วยให้โค้ดเรา Clean ขึ้น Maintain ง่ายขึ้น และที่สำคัญคือ Bug น้อยลงเยอะเลยนะ
พี่แนะนำว่าให้เริ่มจาก Utility Types ก่อนเลย เพราะมันง่ายและใช้บ่อย จากนั้นค่อยไปดู Discriminated Unions และ Conditional Types จะช่วยให้เราเข้าใจ Concept ที่ซับซ้อนขึ้นได้ง่ายขึ้น
เหมาะกับ Project ขนาดกลางถึงใหญ่ ที่มีทีมงานหลายคนช่วยกันพัฒนา และต้องการความยืดหยุ่นและความปลอดภัยของโค้ดสูง ถ้าเป็น Project เล็กๆ อาจจะไม่คุ้มค่าที่จะใช้ Advanced Patterns เพราะมันอาจจะทำให้โค้ดซับซ้อนเกินไป
พี่แนะนำ VS Code เลย เพราะมันมี Extension ที่ช่วย Support Typescript ได้ดีมาก แถมยังมี Auto-Completion และ Error Checking ที่แม่นยำ ช่วยให้เราเขียนโค้ดได้เร็วขึ้นและลด Bug ได้เยอะเลย
Typescript Advanced Patterns เนี่ย เป็นเครื่องมือที่ทรงพลังมาก ถ้าเราใช้มันอย่างถูกต้อง มันจะช่วยให้เราสร้างโปรแกรมที่แข็งแกร่งและยืดหยุ่นได้ แต่ก็ต้องระวังอย่าใช้มันมากเกินไป จนทำให้โค้ดซับซ้อนเกินความจำเป็น
จำไว้ว่าเป้าหมายสูงสุดของการเขียนโปรแกรมคือการสร้างโปรแกรมที่ทำงานได้ดี แก้ไขได้ง่าย และ Maintain ได้นานๆ นะน้อง iCafeForex ก็เหมือนกัน ทำอะไรต้องมีเป้าหมายที่ชัดเจน
สุดท้ายนี้ พี่ขอฝาก SiamCafe Blog ไว้ด้วยนะ ในนั้นมีบทความดีๆ เกี่ยวกับ Programming อีกเยอะเลย