ทำไมต้องเริ่มที่ Type-First Architecture?
เวลาเราสร้างบ้าน เราต้องมีแบบแปลนที่ชัดเจนก่อนเริ่มลงเสาเข็ม การทำซอฟต์แวร์ด้วย React TypeScript ก็เหมือนกัน Type-First Architecture (การออกแบบโครงสร้างข้อมูลก่อนเริ่มเขียนโค้ด) คือการกำหนดหน้าตาข้อมูลให้ชัดเจนตั้งแต่แรก
ถ้าเปรียบเทียบกับการเขียนโปรแกรมทั่วไป เรามักจะเขียนโค้ดไปก่อนแล้วค่อยมาเดาว่าข้อมูลหน้าตาเป็นอย่างไร วิธีนี้ทำให้เกิดบั๊ก (ข้อผิดพลาดของโปรแกรม) ได้ง่ายมาก เพราะเราไม่รู้ว่าข้อมูลที่ส่งไปมาแต่ละส่วนมีหน้าตาเหมือนกันไหม
การกำหนด Type (ชนิดของข้อมูล) ตั้งแต่เริ่มช่วยให้เราคุยกับคอมพิวเตอร์รู้เรื่องขึ้นเยอะ มันช่วยแจ้งเตือนทันทีถ้าเราใส่ข้อมูลผิดที่ผิดทาง ทำให้งานของเรามั่นคงและแก้ไขได้ง่ายในระยะยาว โดยเฉพาะเวลาทำงานเป็นทีมที่ต้องใช้ข้อมูลร่วมกันหลายคน
รู้จักกับ Domain Models หัวใจของข้อมูล
Domain Models (แบบจำลองของข้อมูลหลักในระบบ) คือการสรุปว่าในแอปของเรามี "สิ่งที่จับต้องได้" อะไรบ้าง เช่น ในแอปขายของก็จะมี สินค้า, ตะกร้าสินค้า, และผู้ใช้งาน เราควรสร้างไฟล์แยกสำหรับเก็บพวกนี้เพื่อให้เรียกใช้ได้ทั่วทั้งโปรเจกต์
ลองจินตนาการว่าถ้าเราต้องบอกเพื่อนว่า "คน" หนึ่งคนประกอบด้วยอะไรบ้าง เราก็ต้องบอกว่ามี ชื่อ, อายุ, และอีเมล การเขียนโค้ดก็คือการแปลงความเข้าใจนี้ให้เป็นภาษาคอมพิวเตอร์ผ่านตัวแปรที่เรียกว่า interface (ข้อกำหนดโครงสร้างข้อมูล)
ข้อควรระวัง คืออย่าพยายามใส่ข้อมูลทุกอย่างลงไปในที่เดียว ให้แยกข้อมูลตามหน้าที่ของมันให้ชัดเจน การออกแบบให้ดีตั้งแต่แรกจะช่วยให้เราไม่ต้องกลับมารื้อโค้ดใหม่ตอนที่ระบบเริ่มขยายตัวใหญ่ขึ้นในอนาคต
// ไฟล์ชื่อ types/user.ts
// กำหนดโครงสร้างข้อมูลพื้นฐานของ User ในระบบ
export interface User {
id: string; // รหัสประจำตัว
username: string; // ชื่อผู้ใช้
email: string; // อีเมลสำหรับติดต่อ
isActive: boolean; // สถานะว่ายังใช้งานอยู่ไหม
}
โค้ดด้านบนใช้ interface เพื่อบอกว่า User ต้องมีหน้าตาแบบไหน ทุกครั้งที่เราสร้างข้อมูลผู้ใช้งาน เราต้องทำตามกฎนี้เสมอ ถ้าขาดข้อมูลตัวใดตัวหนึ่งไป โปรแกรมจะฟ้องทันที ทำให้เราไม่ลืมใส่ค่าสำคัญลงไป
ออกแบบ API DTOs เพื่อการสื่อสารที่แม่นยำ
API DTOs (รูปแบบข้อมูลที่รับส่งกับเซิร์ฟเวอร์) คือการกำหนดว่าเวลาเราไปดึงข้อมูลจากฐานข้อมูลมา ข้อมูลนั้นจะมีหน้าตาอย่างไร ต่างจาก Domain Models ตรงที่ข้อมูลจากเซิร์ฟเวอร์อาจจะมีค่าพิเศษ เช่น วันที่สร้างข้อมูล หรือรหัสที่เซิร์ฟเวอร์สร้างให้เอง
มือใหม่มักพลาดโดยการใช้ Domain Models ตัวเดียวกับข้อมูลที่ส่งมาจากเซิร์ฟเวอร์เลย ซึ่งบางครั้งข้อมูลที่เซิร์ฟเวอร์ส่งมาอาจจะเยอะเกินความจำเป็น การแยก DTOs ออกมาจะช่วยให้เราคุมคุณภาพข้อมูลได้ดีกว่า
ถ้าเราออกแบบ DTOs ไว้ดี เราจะรู้ทันทีว่าข้อมูลที่ได้รับมานั้นพร้อมใช้งานเลยไหม หรือต้องแปลงค่าก่อนเอาไปแสดงผลบนหน้าจอ การทำแบบนี้ช่วยให้แอปของเราเสถียรขึ้นและรับมือกับความผิดพลาดจากฝั่งเซิร์ฟเวอร์ได้ดีกว่าเดิม
// ไฟล์ชื่อ types/api.ts
// กำหนดหน้าตาข้อมูลที่รับมาจากเซิร์ฟเวอร์
export interface UserResponseDTO {
id: string;
email: string;
created_at: string; // เซิร์ฟเวอร์มักส่งวันที่เป็นข้อความมาให้
}
โค้ดนี้แสดงให้เห็นว่าข้อมูลจาก API (ช่องทางเชื่อมต่อข้อมูล) อาจมีชื่อฟิลด์ต่างจากในโค้ดหลักของเรา เช่น created_at การแยก DTOs ออกมาทำให้เราจัดการการแปลงข้อมูลให้เป็นรูปแบบที่เหมาะสมก่อนนำไปใช้ในหน้าจอได้สะดวก
สร้าง Reusable UI Components ที่ยืดหยุ่น
Reusable UI Components (ชิ้นส่วนหน้าจอที่นำกลับมาใช้ซ้ำได้) คือการสร้างปุ่มหรือกล่องข้อความที่เปลี่ยนหน้าตาได้ตามค่าที่เราส่งเข้าไป การทำแบบนี้ช่วยให้เราไม่ต้องเขียนโค้ดหน้าจอเดิมซ้ำๆ หลายที่
เปรียบเทียบง่ายๆ เหมือนการมีบล็อกตัวต่อเลโก้ เรามีบล็อกสีแดง สีฟ้า หรือสีเหลือง แต่ละบล็อกมีขนาดต่างกัน แต่เราสามารถหยิบมาประกอบเป็นบ้านหรือรถได้ตามต้องการโดยไม่ต้องสร้างบล็อกใหม่ทุกครั้ง
การใช้ TypeScript ร่วมกับการสร้าง Components (ส่วนประกอบของหน้าจอ) ช่วยให้เรากำหนดได้ว่าปุ่มของเราต้องรับค่าอะไรบ้าง เช่น ต้องรับชื่อปุ่ม หรือต้องรับฟังก์ชันเวลาคลิก ทำให้เพื่อนร่วมทีมใช้โค้ดของเราได้ง่ายและไม่เกิดข้อผิดพลาด
// ไฟล์ชื่อ components/Button.tsx
// สร้างปุ่มที่เปลี่ยนข้อความได้ผ่าน props
interface ButtonProps {
label: string; // ข้อความบนปุ่ม
onClick: () => void; // ฟังก์ชันเมื่อกดปุ่ม
}
export const Button = ({ label, onClick }: ButtonProps) => (
<button onClick={onClick}>{label}</button>
);
โค้ดนี้สร้าง Button ขึ้นมาโดยระบุว่าต้องมี label และ onClick ถ้าใครพยายามใช้ปุ่มนี้โดยไม่ใส่ข้อมูลให้ครบ โปรแกรมจะแจ้งเตือนทันที นี่คือพลังของ TypeScript ที่ช่วยให้การทำงานร่วมกันเป็นเรื่องง่าย
จุดที่มือใหม่มักพลาดในการออกแบบ
ข้อผิดพลาดที่พบบ่อยที่สุดคือการใส่ทุกอย่างลงในไฟล์เดียวจนโค้ดรกเกินไป การเก็บทุกอย่างรวมกันทำให้หาจุดแก้ไขยากและทำให้โปรเจกต์ดูน่ากลัวสำหรับมือใหม่ การจัดระเบียบไฟล์ตามหน้าที่จึงสำคัญที่สุด
อีกจุดคือการใช้ any (ชนิดข้อมูลอะไรก็ได้) ใน TypeScript การทำแบบนี้คือการทำลายข้อดีของระบบทั้งหมด เพราะมันจะทำให้คอมพิวเตอร์เลิกตรวจสอบความถูกต้องของข้อมูล และเราจะกลับไปเจอบั๊กแบบเดิมที่เคยเจอ
สุดท้ายคือการออกแบบที่ซับซ้อนเกินไป (Over-engineering) อย่าพยายามสร้างระบบที่ยืดหยุ่นมากจนเกินความจำเป็น ให้เริ่มจากสิ่งที่ต้องใช้จริงๆ ก่อนแล้วค่อยขยายเพิ่มเมื่อถึงเวลาที่ต้องทำจริงๆ จะช่วยประหยัดเวลาได้มากกว่า
- แยกไฟล์
typesออกจากcomponentsให้ชัดเจน - เลี่ยงการใช้
anyให้มากที่สุดเท่าที่จะทำได้ - ใช้ชื่อตัวแปรที่สื่อความหมายชัดเจน เช่น
UserResponseแทนData
สรุป: เริ่มต้นก้าวแรกให้ถูกทาง
การออกแบบด้วย Type-First Architecture อาจดูเหมือนเสียเวลาในตอนแรก แต่เชื่อเถอะว่ามันคุ้มค่ามากในระยะยาว เพราะมันช่วยลดเวลาการแก้บั๊กและทำให้โค้ดของเราอ่านง่ายขึ้นเยอะสำหรับตัวเราเองและคนในทีม
ลองเอาไปใช้จริงกับโปรเจกต์เล็กๆ เช่น แอปรายการสิ่งที่ต้องทำ (To-do List) เริ่มจากการกำหนด Interface ของงานแต่ละชิ้น แล้วค่อยสร้าง Component สำหรับแสดงผลรายการงานนั้นๆ ดูว่ามันช่วยให้เขียนโค้ดได้ราบรื่นขึ้นอย่างไร
ความเก่งของโปรแกรมเมอร์วัดกันที่การออกแบบระบบให้คนอื่นอ่านออก และการวางโครงสร้างข้อมูลให้แม่นยำตั้งแต่ต้นคือทักษะสำคัญที่แยกมือสมัครเล่นออกจากมืออาชีพ ขอให้สนุกกับการวางรากฐานโปรเจกต์แรกของคุณครับ!