TypeScript เป็นภาษาโปรแกรมที่สร้างขึ้นมาเพื่อเสริมความแข็งแกร่งให้กับ JavaScript โดยการเพิ่มระบบ Type System เข้าไป คุณอาจเคยประสบปัญหาที่โค้ด JavaScript ทำงานผิดพลาดเพราะส่งค่าผิด type เข้าไปในฟังก์ชัน หรือเข้าถึง property ที่ไม่มีอยู่จริงในออบเจ็กต์ TypeScript ช่วยแก้ปัญหานี้ได้โดยการตรวจสอบ type ก่อนที่โค้ดจะถูกรันจริง ทำให้เราสามารถจับข้อผิดพลาดได้ตั้งแต่ตอนเขียนโค้ด
ในบทความนี้ เราจะเริ่มต้นจากพื้นฐานของ Variable Type ไปจนถึงเรื่องที่ซับซ้อนขึ้น เช่น การทำงานกับ JavaScript libraries ที่ไม่มี type definitions การสร้าง type สำหรับ components และการใช้งาน Web API อย่างปลอดภัย
graph TD
A[TypeScript Code] -->|Compilation| B[JavaScript Code]
B -->|Runs in| C[Browser/Node.js]
D[Type Checking] -->|During Compile Time| A
E[Type Definitions] -->|Guides| D
F[JavaScript Libraries] -->|Needs| E
ก่อนที่เราจะเจาะลึกลงไปในรายละเอียด เราต้องเข้าใจก่อนว่า TypeScript จริง ๆ แล้วจะถูกแปลง (compile) เป็น JavaScript ก่อนที่จะรันจริง ระบบ type ทั้งหมดจะถูกใช้เพื่อตรวจสอบความถูกต้องในช่วงเวลาที่เขียนโค้ดและ compile เท่านั้น เมื่อแปลงเป็น JavaScript แล้ว ข้อมูล type ทั้งหมดจะหายไป นี่เป็นเหตุผลว่าทำไมเราต้องมี type definitions แยกต่างหากสำหรับ JavaScript libraries
TypeScript มี primitive types หลัก ๆ ที่คล้ายกับ JavaScript แต่เพิ่มความเข้มงวดในการใช้งาน มาเริ่มต้นกับ type ที่ง่ายที่สุดก่อน
// String Type - ใช้สำหรับข้อความ
let username: string = "สมชาย";
let email: string = "somchai@example.com";
// การพยายามกำหนดค่าที่ไม่ใช่ string จะเกิด error
// username = 123; // Error: Type 'number' is not assignable to type 'string'
// Number Type - ใช้สำหรับตัวเลขทุกชนิด (integer, float, negative)
let age: number = 25;
let price: number = 99.99;
let temperature: number = -15;
// Boolean Type - ใช้สำหรับค่าจริง/เท็จ
let isActive: boolean = true;
let hasPermission: boolean = false;
// Null และ Undefined
let data: null = null;
let notYetDefined: undefined = undefined;
สิ่งที่น่าสนใจคือใน TypeScript เราสามารถให้ตัวแปรมีค่าเป็น null หรือ undefined ได้โดยการรวม type เข้าด้วยกัน ซึ่งเราจะพูดถึงในส่วน Union Types
Array ใน TypeScript สามารถกำหนดได้ว่าสมาชิกภายในต้องเป็น type อะไร มีสองวิธีในการประกาศ array type ซึ่งให้ผลลัพธ์เหมือนกันทุกประการ
// วิธีที่ 1: ใช้ [] หลัง type
let numbers: number[] = [1, 2, 3, 4, 5];
let names: string[] = ["อรุณ", "สมหญิง", "ประยุทธ"];
// วิธีที่ 2: ใช้ Generic Array Type
let scores: Array<number> = [95, 87, 92];
let cities: Array<string> = ["กรุงเทพ", "เชียงใหม่", "ภูเก็ต"];
// Array ของ Mixed Types
let mixed: (string | number)[] = ["text", 123, "more text", 456];
// Multi-dimensional Array
let matrix: number[][] = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
// ตัวอย่างการใช้งาน
numbers.push(6); // OK
// numbers.push("seven"); // Error: Argument of type 'string' is not assignable to parameter of type 'number'
Object types เป็นหัวใจสำคัญของ TypeScript เพราะเราใช้ objects เป็นโครงสร้างหลักในการจัดการข้อมูลส่วนใหญ่ เราสามารถกำหนดรูปร่าง (shape) ของ object ได้อย่างละเอียด
// การกำหนด Object Type แบบ Inline
let user: { name: string; age: number; email: string } = {
name: "สมศักดิ์",
age: 30,
email: "somsak@example.com"
};
// Optional Properties - properties ที่อาจมีหรือไม่มีก็ได้
let product: {
name: string;
price: number;
description?: string; // เครื่องหมาย ? หมายถึง optional
} = {
name: "แล็ปท็อป",
price: 25000
// description ไม่จำเป็นต้องมี
};
// Readonly Properties - properties ที่ไม่สามารถเปลี่ยนแปลงค่าได้หลังจากสร้าง
let config: {
readonly apiKey: string;
readonly endpoint: string;
timeout: number;
} = {
apiKey: "abc123xyz",
endpoint: "https://api.example.com",
timeout: 5000
};
// config.apiKey = "new-key"; // Error: Cannot assign to 'apiKey' because it is a read-only property
config.timeout = 10000; // OK - timeout ไม่ได้เป็น readonly
เมื่อเราต้องใช้ object type ซ้ำ ๆ หลายที่ การเขียน type แบบ inline ทุกครั้งจะทำให้โค้ดยุ่งยาก Type Aliases ช่วยให้เราสามารถตั้งชื่อให้กับ type และนำกลับมาใช้ได้ง่าย ๆ
// การสร้าง Type Alias
type User = {
id: number;
username: string;
email: string;
isActive: boolean;
createdAt: Date;
};
// ตอนนี้เราสามารถใช้ User type ได้เหมือนเป็น type ธรรมดา
let admin: User = {
id: 1,
username: "admin",
email: "admin@example.com",
isActive: true,
createdAt: new Date()
};
let customer: User = {
id: 2,
username: "customer01",
email: "customer@example.com",
isActive: true,
createdAt: new Date()
};
// Type Alias สำหรับ Function
type CalculatorFunction = (a: number, b: number) => number;
const add: CalculatorFunction = (a, b) => a + b;
const subtract: CalculatorFunction = (a, b) => a - b;
const multiply: CalculatorFunction = (a, b) => a * b;
Interface คล้ายกับ Type Alias มาก แต่มีความสามารถพิเศษบางอย่างที่แตกต่างกัน Interface มักถูกใช้สำหรับการกำหนดรูปร่างของ objects และสามารถถูก extend ได้
// การสร้าง Interface
interface Product {
id: number;
name: string;
price: number;
category: string;
}
// Interface สามารถ extend ได้
interface DigitalProduct extends Product {
downloadUrl: string;
fileSize: number;
}
interface PhysicalProduct extends Product {
weight: number;
dimensions: {
width: number;
height: number;
depth: number;
};
}
// ตัวอย่างการใช้งาน
const ebook: DigitalProduct = {
id: 101,
name: "TypeScript Guide",
price: 299,
category: "Books",
downloadUrl: "https://example.com/download/ts-guide.pdf",
fileSize: 2048576 // bytes
};
const laptop: PhysicalProduct = {
id: 201,
name: "MacBook Pro",
price: 89900,
category: "Electronics",
weight: 1.4, // kg
dimensions: {
width: 30.41,
height: 1.55,
depth: 21.24
}
};
ความแตกต่างหลักระหว่าง Type Alias และ Interface คือ Interface สามารถถูกเปิดใหม่ (declaration merging) ได้ ซึ่งมีประโยชน์เมื่อต้องการเพิ่ม properties ให้กับ interface ที่มีอยู่แล้ว
// Declaration Merging - สามารถประกาศ Interface ชื่อเดียวกันหลายครั้ง
interface Window {
customProperty: string;
}
interface Window {
anotherCustomProperty: number;
}
// ตอนนี้ Window จะมีทั้ง customProperty และ anotherCustomProperty
Union Types ช่วยให้เราสามารถกำหนดว่าตัวแปรสามารถเป็นได้หลาย type โดยใช้เครื่องหมาย | คั่นระหว่าง types นี่เป็นเครื่องมือที่ทรงพลังมากเมื่อต้องรับมือกับข้อมูลที่มีความหลากหลาย
// Union Type พื้นฐาน
let id: string | number;
id = "USER001"; // OK
id = 12345; // OK
// id = true; // Error: Type 'boolean' is not assignable to type 'string | number'
// Union Type กับ Function Parameters
function formatId(id: string | number): string {
// Type Guard - ตรวจสอบ type ก่อนใช้งาน
if (typeof id === "string") {
return id.toUpperCase(); // ใช้ method ของ string ได้อย่างปลอดภัย
} else {
return `ID-${id.toString().padStart(6, "0")}`; // แปลง number เป็น string format
}
}
console.log(formatId("abc123")); // "ABC123"
console.log(formatId(42)); // "ID-000042"
// Union Type กับ Objects
type SuccessResponse = {
status: "success";
data: any;
};
type ErrorResponse = {
status: "error";
message: string;
errorCode: number;
};
type ApiResponse = SuccessResponse | ErrorResponse;
function handleResponse(response: ApiResponse) {
// Type Narrowing โดยใช้ property ที่แตกต่างกัน
if (response.status === "success") {
console.log("ข้อมูล:", response.data);
} else {
console.error(`Error ${response.errorCode}: ${response.message}`);
}
}
ตรงข้ามกับ Union Types คือ Intersection Types ที่ใช้เครื่องหมาย & เพื่อรวม types เข้าด้วยกัน ผลลัพธ์จะเป็น type ที่มี properties ของทุก types ที่รวมกัน
// Intersection Types
type HasId = {
id: number;
};
type HasTimestamps = {
createdAt: Date;
updatedAt: Date;
};
type HasAuthor = {
authorId: number;
authorName: string;
};
// รวม types เข้าด้วยกัน
type BlogPost = HasId & HasTimestamps & HasAuthor & {
title: string;
content: string;
tags: string[];
};
const article: BlogPost = {
// จาก HasId
id: 1,
// จาก HasTimestamps
createdAt: new Date(),
updatedAt: new Date(),
// จาก HasAuthor
authorId: 101,
authorName: "สมชาย ใจดี",
// Properties เฉพาะ
title: "เรียนรู้ TypeScript",
content: "TypeScript เป็นภาษาที่ยอดเยี่ยม...",
tags: ["typescript", "programming", "tutorial"]
};
Literal Types ช่วยให้เราสามารถกำหนดว่าตัวแปรสามารถมีค่าเป็นค่าเฉพาะเจาะจงได้เท่านั้น ไม่ใช่แค่ type ทั่วไป สิ่งนี้มีประโยชน์มากเมื่อต้องการจำกัดค่าที่สามารถใช้ได้
// String Literal Types
type Status = "pending" | "approved" | "rejected";
type Direction = "north" | "south" | "east" | "west";
let orderStatus: Status = "pending"; // OK
// orderStatus = "cancelled"; // Error: Type '"cancelled"' is not assignable to type 'Status'
// Number Literal Types
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
function rollDice(): DiceRoll {
return (Math.floor(Math.random() * 6) + 1) as DiceRoll;
}
// Boolean Literal Types (ใช้น้อยแต่ก็มีประโยชน์)
type AlwaysTrue = true;
// การผสม Literal Types กับ Objects
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
interface ApiRequest {
method: HttpMethod;
url: string;
headers?: Record<string, string>;
body?: any;
}
const fetchUser: ApiRequest = {
method: "GET",
url: "/api/users/123"
};
const createUser: ApiRequest = {
method: "POST",
url: "/api/users",
headers: {
"Content-Type": "application/json"
},
body: {
name: "นายใหม่",
email: "newnew@example.com"
}
};
Tuple เป็น array ที่มีความยาวและ type ของแต่ละตำแหน่งถูกกำหนดไว้อย่างชัดเจน มีประโยชน์เมื่อต้องการจัดกลุ่มข้อมูลที่เกี่ยวข้องกันแต่มี type แตกต่างกัน
// Tuple พื้นฐาน
let coordinate: [number, number] = [13.7563, 100.5018]; // latitude, longitude
// Tuple กับ Label (TypeScript 4.0+)
let user: [id: number, name: string, isActive: boolean] = [1, "สมชาย", true];
// Optional Elements ใน Tuple
let response: [status: number, message: string, data?: any] = [200, "Success"];
// หรือ
response = [200, "Success", { users: [] }];
// Rest Elements ใน Tuple
type StringNumberPair = [string, ...number[]];
let scores: StringNumberPair = ["คะแนนสอบ", 85, 90, 92, 88];
// Tuple ที่มีความซับซ้อน
type DatabaseRow = [
id: number,
name: string,
email: string,
createdAt: Date,
metadata: Record<string, any>
];
const userRow: DatabaseRow = [
1,
"สมศักดิ์",
"somsak@example.com",
new Date("2024-01-01"),
{ lastLogin: new Date(), loginCount: 42 }
];
// Destructuring กับ Tuple
const [userId, userName, userEmail] = userRow;
console.log(`User: ${userName} (${userEmail})`);
Enum เป็นวิธีในการตั้งชื่อให้กับชุดของค่าคงที่ ทำให้โค้ดอ่านง่ายและเข้าใจได้ดีขึ้น โดยเฉพาะเมื่อต้องทำงานกับค่าที่มีความหมายเฉพาะ
// Numeric Enum - มีค่าเป็นตัวเลข
enum UserRole {
Guest, // 0
User, // 1
Moderator, // 2
Admin // 3
}
let myRole: UserRole = UserRole.Admin;
console.log(myRole); // 3
// String Enum - มีค่าเป็น string
enum OrderStatus {
Pending = "PENDING",
Processing = "PROCESSING",
Shipped = "SHIPPED",
Delivered = "DELIVERED",
Cancelled = "CANCELLED"
}
function updateOrderStatus(orderId: number, status: OrderStatus) {
console.log(`คำสั่งซื้อ ${orderId} ถูกอัพเดตเป็น ${status}`);
}
updateOrderStatus(12345, OrderStatus.Shipped);
// Heterogeneous Enum - ผสม number และ string (ใช้น้อยและไม่แนะนำ)
enum Mixed {
No = 0,
Yes = "YES"
}
// Const Enum - ประหยัด output size
const enum LogLevel {
Debug,
Info,
Warning,
Error
}
function log(message: string, level: LogLevel) {
if (level >= LogLevel.Warning) {
console.error(message);
} else {
console.log(message);
}
}
เมื่อเราต้องการใช้ JavaScript library ใน TypeScript project ปัญหาแรกที่เจอคือ TypeScript ไม่รู้ว่า library นั้นมี functions อะไร parameters เป็น type อะไร หรือ return type เป็นอะไร ดังนั้นเราจึงต้องมี Type Definitions เพื่อบอก TypeScript ให้รู้จักกับ library นั้น ๆ
graph LR
A[JavaScript Library] -->|No Types| B[TypeScript Code]
B -->|Compile Error| C[Type Unknown]
D[Type Definitions] -->|Provides Types| B
B -->|Successful Compile| E[JavaScript Output]
DefinitelyTyped เป็น repository ขนาดใหญ่ที่รวบรวม type definitions ของ JavaScript libraries นับพันตัว เราสามารถติดตั้ง type definitions ผ่าน npm ด้วย package ที่ขึ้นต้นด้วย @types/
# ติดตั้ง library และ type definitions
npm install lodash
npm install --save-dev @types/lodash
# หรือติดตั้งพร้อมกัน
npm install lodash
npm install -D @types/lodash
หลังจากติดตั้งแล้ว เราสามารถใช้ lodash ได้โดยมี type checking ครบถ้วน
import _ from 'lodash';
// TypeScript รู้จัก types ทั้งหมดของ lodash
const numbers = [1, 2, 3, 4, 5];
const doubled = _.map(numbers, n => n * 2);
// doubled มี type เป็น number[]
const users = [
{ name: "สมชาย", age: 25 },
{ name: "สมหญิง", age: 30 },
{ name: "ประยุทธ", age: 28 }
];
// TypeScript รู้ว่า sortedUsers มี type เป็นอะไร
const sortedUsers = _.sortBy(users, 'age');
// TypeScript จะเตือนถ้าเราพยายามเข้าถึง property ที่ไม่มี
// const invalid = sortedUsers[0].address; // Error: Property 'address' does not exist
บางครั้ง library ที่เราใช้อาจไม่มี type definitions บน DefinitelyTyped หรือเราอาจต้องการสร้าง types สำหรับโมดูลของเราเอง เราสามารถสร้าง type definitions ได้เองผ่านไฟล์ .d.ts
สมมติว่าเรามี JavaScript library ชื่อ my-utils ที่ไม่มี types เราจะสร้างไฟล์ my-utils.d.ts ขึ้นมา
// my-utils.d.ts - Declaration File
// ประกาศ module
declare module 'my-utils' {
// ประกาศ function
export function capitalize(text: string): string;
export function sum(numbers: number[]): number;
export function debounce<T extends (...args: any[]) => any>(
func: T,
delay: number
): (...args: Parameters<T>) => void;
// ประกาศ class
export class Calculator {
constructor(initialValue?: number);
add(value: number): Calculator;
subtract(value: number): Calculator;
multiply(value: number): Calculator;
divide(value: number): Calculator;
result(): number;
}
// ประกาศ interface
export interface Config {
apiUrl: string;
timeout: number;
retries?: number;
}
// ประกาศ type
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
// ประกาศ constant
export const VERSION: string;
}
ตอนนี้เราสามารถใช้ my-utils ได้พร้อม type checking
import { capitalize, Calculator, Config } from 'my-utils';
// TypeScript รู้ type ของทุกอย่าง
const name = capitalize("สมชาย"); // type: string
const calc = new Calculator(10);
const result = calc.add(5).multiply(2).result(); // type: number
const config: Config = {
apiUrl: "https://api.example.com",
timeout: 5000,
retries: 3
};
บางครั้งเราต้องการประกาศ types สำหรับตัวแปรหรือฟังก์ชันที่มีอยู่ใน global scope (เช่น จาก script tag ใน HTML) เราสามารถใช้ ambient declarations ได้
// globals.d.ts
// ประกาศ global variable
declare const API_ENDPOINT: string;
declare const DEBUG_MODE: boolean;
// ประกาศ global function
declare function legacyAlert(message: string): void;
// ประกาศ global object
declare namespace MyApp {
interface User {
id: number;
name: string;
}
function getCurrentUser(): User | null;
function logout(): void;
}
// ขยาย Window interface
interface Window {
myCustomProperty: string;
myCustomFunction: (value: number) => string;
}
หลังจากประกาศแล้ว เราสามารถใช้ได้เลยโดยไม่ต้อง import
// ใช้ global variables
console.log(API_ENDPOINT);
if (DEBUG_MODE) {
console.log("Debug mode is on");
}
// ใช้ global functions
legacyAlert("สวัสดี!");
// ใช้ global namespace
const currentUser = MyApp.getCurrentUser();
if (currentUser) {
console.log(currentUser.name);
}
// ใช้ extended Window properties
window.myCustomProperty = "Hello";
const formatted = window.myCustomFunction(42);
บางครั้งเราต้องการเพิ่ม types ให้กับ module ที่มีอยู่แล้ว (เช่น เพิ่ม method ใหม่ให้ Express) เราสามารถใช้ Module Augmentation ได้
// express.d.ts - เพิ่ม types ให้ Express
import { User } from './user'; // นำเข้า User type ของเรา
// Augment Express module
declare module 'express-serve-static-core' {
// เพิ่ม property ให้ Request interface
interface Request {
user?: User;
sessionId?: string;
requestTime?: Date;
}
// เพิ่ม method ให้ Response interface
interface Response {
sendSuccess<T>(data: T, message?: string): Response;
sendError(error: string, statusCode?: number): Response;
}
}
ตอนนี้เราสามารถใช้ properties และ methods ใหม่เหล่านี้ได้
import express, { Request, Response } from 'express';
const app = express();
app.use((req: Request, res: Response, next) => {
// เพิ่ม requestTime ให้ทุก request
req.requestTime = new Date();
next();
});
app.get('/api/user', (req: Request, res: Response) => {
// TypeScript รู้จัก req.user และ res.sendSuccess
if (req.user) {
res.sendSuccess(req.user, "ดึงข้อมูลผู้ใช้สำเร็จ");
} else {
res.sendError("ไม่พบผู้ใช้", 404);
}
});
Generic Types เป็นหนึ่งในฟีเจอร์ที่ทรงพลังที่สุดของ TypeScript ช่วยให้เราสร้าง components หรือฟังก์ชันที่สามารถทำงานกับ types ที่หลากหลายได้โดยยังคงความปลอดภัยของ type ไว้
// Generic Function พื้นฐาน
function identity<T>(value: T): T {
return value;
}
// TypeScript จะอนุมาน type จากการใช้งาน
const numberResult = identity(42); // type: number
const stringResult = identity("Hello"); // type: string
const arrayResult = identity([1, 2, 3]); // type: number[]
// หรือเราสามารถระบุ type เองได้
const explicitResult = identity<boolean>(true); // type: boolean
// Generic Function ที่ซับซ้อนขึ้น
function wrapInArray<T>(value: T): T[] {
return [value];
}
const numbers = wrapInArray(5); // type: number[]
const words = wrapInArray("hello"); // type: string[]
// Generic Function กับ Multiple Type Parameters
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
const coordinate = pair(10, 20); // type: [number, number]
const userInfo = pair("id", 12345); // type: [string, number]
const mixed = pair({ name: "John" }, [1, 2, 3]); // type: [{ name: string }, number[]]
บางครั้งเราต้องการจำกัดว่า generic type ต้องมีคุณสมบัติบางอย่าง เราใช้ extends เพื่อกำหนดข้อจำกัด
// ต้องการให้ T มี length property
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(value: T): void {
console.log(`ความยาว: ${value.length}`);
}
logLength("Hello"); // OK - string มี length
logLength([1, 2, 3]); // OK - array มี length
logLength({ length: 10, data: "something" }); // OK - object มี length
// logLength(123); // Error: number ไม่มี length property
// Generic Constraint กับ Object Properties
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const person = {
name: "สมชาย",
age: 30,
city: "กรุงเทพ"
};
const name = getProperty(person, "name"); // type: string
const age = getProperty(person, "age"); // type: number
// const invalid = getProperty(person, "address"); // Error: "address" ไม่ใช่ key ของ person
// Generic Class พื้นฐาน
class Box<T> {
private content: T;
constructor(value: T) {
this.content = value;
}
getValue(): T {
return this.content;
}
setValue(value: T): void {
this.content = value;
}
}
const numberBox = new Box<number>(42);
console.log(numberBox.getValue()); // 42
numberBox.setValue(100); // OK
// numberBox.setValue("text"); // Error: string ไม่ได้เป็น number
const stringBox = new Box<string>("Hello");
console.log(stringBox.getValue()); // "Hello"
// Generic Class ที่ซับซ้อน - Queue
class Queue<T> {
private items: T[] = [];
enqueue(item: T): void {
this.items.push(item);
}
dequeue(): T | undefined {
return this.items.shift();
}
peek(): T | undefined {
return this.items[0];
}
size(): number {
return this.items.length;
}
isEmpty(): boolean {
return this.items.length === 0;
}
}
// ใช้กับ type ต่าง ๆ
const numberQueue = new Queue<number>();
numberQueue.enqueue(1);
numberQueue.enqueue(2);
numberQueue.enqueue(3);
console.log(numberQueue.dequeue()); // 1
interface Task {
id: number;
title: string;
priority: number;
}
const taskQueue = new Queue<Task>();
taskQueue.enqueue({ id: 1, title: "งาน A", priority: 1 });
taskQueue.enqueue({ id: 2, title: "งาน B", priority: 2 });
// Generic Interface
interface Result<T> {
success: boolean;
data: T;
message: string;
timestamp: Date;
}
// ใช้กับ type ต่าง ๆ
type UserResult = Result<{
id: number;
name: string;
email: string;
}>;
type ProductListResult = Result<{
products: Array<{
id: number;
name: string;
price: number;
}>;
total: number;
}>;
// Function ที่ return Generic Interface
function createSuccessResult<T>(data: T, message: string = "Success"): Result<T> {
return {
success: true,
data,
message,
timestamp: new Date()
};
}
const userResult = createSuccessResult({
id: 1,
name: "สมชาย",
email: "somchai@example.com"
});
const numberArrayResult = createSuccessResult([1, 2, 3, 4, 5], "ดึงข้อมูลสำเร็จ");
เมื่อเราใช้ TypeScript กับ React เราจะได้ประโยชน์จาก type checking ในหลายส่วน ตั้งแต่ props, state, events จนถึง refs
import React, { useState, useEffect, useRef, ReactNode } from 'react';
// Props Type สำหรับ Functional Component
interface ButtonProps {
label: string;
onClick: () => void;
disabled?: boolean;
variant?: 'primary' | 'secondary' | 'danger';
icon?: ReactNode;
}
// Functional Component กับ Props Type
const Button: React.FC<ButtonProps> = ({
label,
onClick,
disabled = false,
variant = 'primary',
icon
}) => {
return (
<button
onClick={onClick}
disabled={disabled}
className={`btn btn-${variant}`}
>
{icon && <span className="icon">{icon}</span>}
{label}
</button>
);
};
// Component กับ State
interface User {
id: number;
name: string;
email: string;
avatar?: string;
}
interface UserCardProps {
userId: number;
}
const UserCard: React.FC<UserCardProps> = ({ userId }) => {
// useState กับ Generic Type
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetchUser(userId)
.then(data => {
setUser(data);
setLoading(false);
})
.catch(err => {
setError(err.message);
setLoading(false);
});
}, [userId]);
if (loading) return <div>กำลังโหลด...</div>;
if (error) return <div>เกิดข้อผิดพลาด: {error}</div>;
if (!user) return <div>ไม่พบข้อมูล</div>;
return (
<div className="user-card">
{user.avatar && <img src={user.avatar} alt={user.name} />}
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
);
};
// Event Handler Types
interface FormProps {
onSubmit: (data: { name: string; email: string }) => void;
}
const ContactForm: React.FC<FormProps> = ({ onSubmit }) => {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
// Event Handler กับ proper types
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
onSubmit({ name, email });
};
const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setName(e.target.value);
};
const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setEmail(e.target.value);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={name}
onChange={handleNameChange}
placeholder="ชื่อ"
/>
<input
type="email"
value={email}
onChange={handleEmailChange}
placeholder="อีเมล"
/>
<button type="submit">ส่ง</button>
</form>
);
};
// Ref Types
const AutoFocusInput: React.FC = () => {
// useRef กับ proper type
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
// TypeScript รู้ว่า current อาจเป็น null
inputRef.current?.focus();
}, []);
return <input ref={inputRef} type="text" />;
};
// Helper function (mock)
async function fetchUser(userId: number): Promise<User> {
// Mock implementation
return {
id: userId,
name: "สมชาย",
email: "somchai@example.com"
};
}
import React, { ReactNode } from 'react';
// Component ที่รับ children
interface CardProps {
title: string;
children: ReactNode;
footer?: ReactNode;
}
const Card: React.FC<CardProps> = ({ title, children, footer }) => {
return (
<div className="card">
<div className="card-header">
<h2>{title}</h2>
</div>
<div className="card-body">
{children}
</div>
{footer && (
<div className="card-footer">
{footer}
</div>
)}
</div>
);
};
// การใช้งาน
const App: React.FC = () => {
return (
<Card
title="ข้อมูลผู้ใช้"
footer={<button>บันทึก</button>}
>
<p>ชื่อ: สมชาย</p>
<p>อีเมล: somchai@example.com</p>
</Card>
);
};
// Component ที่รับ function children (Render Props Pattern)
interface DataFetcherProps<T> {
url: string;
children: (data: T | null, loading: boolean, error: string | null) => ReactNode;
}
function DataFetcher<T>({ url, children }: DataFetcherProps<T>) {
const [data, setData] = React.useState<T | null>(null);
const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState<string | null>(null);
React.useEffect(() => {
fetch(url)
.then(res => res.json())
.then(data => {
setData(data);
setLoading(false);
})
.catch(err => {
setError(err.message);
setLoading(false);
});
}, [url]);
return <>{children(data, loading, error)}</>;
}
// การใช้งาน DataFetcher
interface Todo {
id: number;
title: string;
completed: boolean;
}
const TodoList: React.FC = () => {
return (
<DataFetcher<Todo[]> url="/api/todos">
{(todos, loading, error) => {
if (loading) return <div>กำลังโหลด...</div>;
if (error) return <div>Error: {error}</div>;
if (!todos) return <div>ไม่มีข้อมูล</div>;
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
{todo.title} - {todo.completed ? '✓' : '○'}
</li>
))}
</ul>
);
}}
</DataFetcher>
);
};
import { useState, useEffect, useCallback } from 'react';
// Custom Hook สำหรับ Local Storage
function useLocalStorage<T>(
key: string,
initialValue: T
): [T, (value: T) => void] {
// อ่านค่าเริ่มต้นจาก localStorage
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
// บันทึกค่าลง localStorage
const setValue = useCallback((value: T) => {
try {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error(error);
}
}, [key]);
return [storedValue, setValue];
}
// Custom Hook สำหรับ API Fetching
interface UseFetchResult<T> {
data: T | null;
loading: boolean;
error: string | null;
refetch: () => void;
}
function useFetch<T>(url: string): UseFetchResult<T> {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchData = useCallback(async () => {
try {
setLoading(true);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const json = await response.json();
setData(json);
setError(null);
} catch (e) {
setError(e instanceof Error ? e.message : 'เกิดข้อผิดพลาดที่ไม่ทราบสาเหตุ');
} finally {
setLoading(false);
}
}, [url]);
useEffect(() => {
fetchData();
}, [fetchData]);
return { data, loading, error, refetch: fetchData };
}
// ตัวอย่างการใช้งาน Custom Hooks
interface Settings {
theme: 'light' | 'dark';
language: 'th' | 'en';
notifications: boolean;
}
const SettingsPage: React.FC = () => {
// ใช้ useLocalStorage hook
const [settings, setSettings] = useLocalStorage<Settings>('app-settings', {
theme: 'light',
language: 'th',
notifications: true
});
// ใช้ useFetch hook
const { data: user, loading, error } = useFetch<User>('/api/user');
const updateTheme = (theme: 'light' | 'dark') => {
setSettings({ ...settings, theme });
};
return (
<div>
<h1>การตั้งค่า</h1>
{loading && <p>กำลังโหลด...</p>}
{error && <p>Error: {error}</p>}
{user && <p>ผู้ใช้: {user.name}</p>}
<div>
<label>
ธีม:
<select
value={settings.theme}
onChange={(e) => updateTheme(e.target.value as 'light' | 'dark')}
>
<option value="light">สว่าง</option>
<option value="dark">มืด</option>
</select>
</label>
</div>
</div>
);
};
TypeScript มี type definitions สำหรับ Web APIs ที่มีมาพร้อมกับ browser ทั้งหมดอยู่แล้ว เราไม่ต้องติดตั้งอะไรเพิ่มเติม มาดูวิธีการใช้งาน Web APIs ต่าง ๆ อย่างปลอดภัยกับ TypeScript
// Query Selectors กับ Type Assertions
const button = document.querySelector<HTMLButtonElement>('#submit-button');
const input = document.querySelector<HTMLInputElement>('#username');
const form = document.querySelector<HTMLFormElement>('#login-form');
// ต้องตรวจสอบว่า element มีอยู่จริงก่อนใช้งาน
if (button) {
button.disabled = false;
button.addEventListener('click', (e: MouseEvent) => {
console.log('Button clicked!', e.clientX, e.clientY);
});
}
if (input) {
input.value = 'default value';
input.addEventListener('input', (e: Event) => {
// Type Narrowing
const target = e.target as HTMLInputElement;
console.log('Current value:', target.value);
});
}
// querySelectorAll returns NodeList
const listItems = document.querySelectorAll<HTMLLIElement>('.list-item');
listItems.forEach((item, index) => {
item.textContent = `Item ${index + 1}`;
item.style.color = 'blue';
});
// การสร้าง elements
const div = document.createElement('div');
div.className = 'container';
div.id = 'main-container';
div.innerHTML = '<h1>สวัสดี</h1>';
const img = document.createElement('img');
img.src = 'photo.jpg';
img.alt = 'รูปภาพ';
img.width = 300;
img.height = 200;
// Event Listeners กับ proper types
const handleClick = (event: MouseEvent) => {
console.log('Clicked at', event.pageX, event.pageY);
};
const handleKeyPress = (event: KeyboardEvent) => {
if (event.key === 'Enter') {
console.log('Enter key pressed!');
}
};
document.addEventListener('click', handleClick);
document.addEventListener('keypress', handleKeyPress);
// Fetch API กับ Generic Types
interface User {
id: number;
name: string;
email: string;
phone?: string;
}
interface ApiResponse<T> {
status: 'success' | 'error';
data: T;
message?: string;
}
// Function สำหรับ GET request
async function fetchUser(userId: number): Promise<User> {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: ApiResponse<User> = await response.json();
if (data.status === 'error') {
throw new Error(data.message || 'Unknown error');
}
return data.data;
} catch (error) {
if (error instanceof Error) {
console.error('Failed to fetch user:', error.message);
}
throw error;
}
}
// Function สำหรับ POST request
interface CreateUserData {
name: string;
email: string;
password: string;
}
async function createUser(userData: CreateUserData): Promise<User> {
const response = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData)
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || 'Failed to create user');
}
const result: ApiResponse<User> = await response.json();
return result.data;
}
// Function สำหรับ file upload
async function uploadFile(file: File): Promise<string> {
const formData = new FormData();
formData.append('file', file);
formData.append('description', 'User uploaded file');
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error('Upload failed');
}
const result: ApiResponse<{ url: string }> = await response.json();
return result.data.url;
}
// การใช้งาน
async function main() {
try {
// Fetch user
const user = await fetchUser(123);
console.log('User:', user.name);
// Create new user
const newUser = await createUser({
name: 'สมชาย ใจดี',
email: 'somchai@example.com',
password: 'secure123'
});
console.log('Created user:', newUser);
} catch (error) {
console.error('Error:', error);
}
}
// Utility functions สำหรับ Storage
class StorageManager<T> {
constructor(
private storage: Storage,
private key: string
) {}
// บันทึกข้อมูล
save(data: T): void {
try {
const serialized = JSON.stringify(data);
this.storage.setItem(this.key, serialized);
} catch (error) {
console.error('Failed to save to storage:', error);
}
}
// อ่านข้อมูล
load(): T | null {
try {
const item = this.storage.getItem(this.key);
return item ? JSON.parse(item) : null;
} catch (error) {
console.error('Failed to load from storage:', error);
return null;
}
}
// ลบข้อมูล
remove(): void {
this.storage.removeItem(this.key);
}
// เคลียร์ทั้งหมด
clear(): void {
this.storage.clear();
}
}
// ตัวอย่างการใช้งาน
interface UserPreferences {
theme: 'light' | 'dark';
fontSize: number;
language: 'th' | 'en';
}
const prefsManager = new StorageManager<UserPreferences>(
localStorage,
'user-preferences'
);
// บันทึกการตั้งค่า
prefsManager.save({
theme: 'dark',
fontSize: 16,
language: 'th'
});
// อ่านการตั้งค่า
const prefs = prefsManager.load();
if (prefs) {
console.log('Current theme:', prefs.theme);
}
// Shopping Cart example
interface CartItem {
productId: number;
name: string;
price: number;
quantity: number;
}
interface ShoppingCart {
items: CartItem[];
total: number;
lastUpdated: string;
}
class CartManager {
private storage: StorageManager<ShoppingCart>;
constructor() {
this.storage = new StorageManager<ShoppingCart>(
localStorage,
'shopping-cart'
);
}
addItem(item: CartItem): void {
const cart = this.storage.load() || this.createEmptyCart();
const existingItem = cart.items.find(i => i.productId === item.productId);
if (existingItem) {
existingItem.quantity += item.quantity;
} else {
cart.items.push(item);
}
cart.total = this.calculateTotal(cart.items);
cart.lastUpdated = new Date().toISOString();
this.storage.save(cart);
}
removeItem(productId: number): void {
const cart = this.storage.load();
if (!cart) return;
cart.items = cart.items.filter(item => item.productId !== productId);
cart.total = this.calculateTotal(cart.items);
cart.lastUpdated = new Date().toISOString();
this.storage.save(cart);
}
getCart(): ShoppingCart {
return this.storage.load() || this.createEmptyCart();
}
clearCart(): void {
this.storage.remove();
}
private createEmptyCart(): ShoppingCart {
return {
items: [],
total: 0,
lastUpdated: new Date().toISOString()
};
}
private calculateTotal(items: CartItem[]): number {
return items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
}
}
// การใช้งาน CartManager
const cartManager = new CartManager();
cartManager.addItem({
productId: 101,
name: 'แล็ปท็อป',
price: 25000,
quantity: 1
});
cartManager.addItem({
productId: 102,
name: 'เมาส์',
price: 500,
quantity: 2
});
const currentCart = cartManager.getCart();
console.log('ตะกร้าสินค้า:', currentCart);
console.log('ยอดรวม:', currentCart.total, 'บาท');
// การจัดการ Events อย่างปลอดภัย
function handleFormSubmit(event: Event): void {
event.preventDefault();
// Type Guard เพื่อตรวจสอบว่าเป็น HTMLFormElement
if (event.target instanceof HTMLFormElement) {
const form = event.target;
const formData = new FormData(form);
// แปลง FormData เป็น object
const data: Record<string, string> = {};
formData.forEach((value, key) => {
data[key] = value.toString();
});
console.log('Form data:', data);
}
}
// Event Handler กับ Input Elements
function handleInput(event: Event): void {
const target = event.target;
// Type Guard สำหรับ HTMLInputElement
if (target instanceof HTMLInputElement) {
console.log('Input value:', target.value);
console.log('Input type:', target.type);
if (target.type === 'checkbox') {
console.log('Checked:', target.checked);
}
}
// Type Guard สำหรับ HTMLTextAreaElement
if (target instanceof HTMLTextAreaElement) {
console.log('Textarea value:', target.value);
console.log('Rows:', target.rows);
}
// Type Guard สำหรับ HTMLSelectElement
if (target instanceof HTMLSelectElement) {
console.log('Selected value:', target.value);
console.log('Selected index:', target.selectedIndex);
}
}
// Custom Event Types
interface CustomEventDetail {
userId: number;
action: string;
timestamp: Date;
}
function dispatchCustomEvent(detail: CustomEventDetail): void {
const event = new CustomEvent('userAction', {
detail,
bubbles: true,
cancelable: true
});
document.dispatchEvent(event);
}
function handleCustomEvent(event: Event): void {
// Type assertion สำหรับ CustomEvent
if (event instanceof CustomEvent) {
const detail = event.detail as CustomEventDetail;
console.log('User action:', detail);
}
}
// การใช้งาน
document.addEventListener('userAction', handleCustomEvent);
dispatchCustomEvent({
userId: 123,
action: 'login',
timestamp: new Date()
});
// Geolocation API กับ TypeScript
interface LocationCoordinates {
latitude: number;
longitude: number;
accuracy: number;
altitude: number | null;
altitudeAccuracy: number | null;
heading: number | null;
speed: number | null;
}
function getCurrentLocation(): Promise<LocationCoordinates> {
return new Promise((resolve, reject) => {
if (!navigator.geolocation) {
reject(new Error('Geolocation is not supported by this browser'));
return;
}
navigator.geolocation.getCurrentPosition(
(position: GeolocationPosition) => {
const coords: LocationCoordinates = {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy,
altitude: position.coords.altitude,
altitudeAccuracy: position.coords.altitudeAccuracy,
heading: position.coords.heading,
speed: position.coords.speed
};
resolve(coords);
},
(error: GeolocationPositionError) => {
let errorMessage = 'Unknown error';
switch (error.code) {
case error.PERMISSION_DENIED:
errorMessage = 'User denied the request for Geolocation';
break;
case error.POSITION_UNAVAILABLE:
errorMessage = 'Location information is unavailable';
break;
case error.TIMEOUT:
errorMessage = 'The request to get user location timed out';
break;
}
reject(new Error(errorMessage));
},
{
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0
}
);
});
}
// การใช้งาน
async function showUserLocation() {
try {
const location = await getCurrentLocation();
console.log('ตำแหน่งปัจจุบัน:');
console.log('ละติจูด:', location.latitude);
console.log('ลองจิจูด:', location.longitude);
console.log('ความแม่นยำ:', location.accuracy, 'เมตร');
} catch (error) {
if (error instanceof Error) {
console.error('ไม่สามารถรับตำแหน่งได้:', error.message);
}
}
}
TypeScript มา utility types ในตัวที่ช่วยให้เราจัดการกับ types ได้ง่ายขึ้น มาดูที่สำคัญ ๆ กัน
interface User {
id: number;
name: string;
email: string;
age: number;
address: string;
}
// Partial - ทำให้ properties ทั้งหมดเป็น optional
type PartialUser = Partial<User>;
// เทียบเท่ากับ:
// {
// id?: number;
// name?: string;
// email?: string;
// age?: number;
// address?: string;
// }
function updateUser(id: number, updates: Partial<User>): void {
// สามารถส่ง properties ที่ต้องการอัพเดตเท่านั้น
console.log('Updating user', id, 'with', updates);
}
updateUser(1, { name: 'ชื่อใหม่' }); // OK
updateUser(1, { email: 'new@email.com', age: 31 }); // OK
// Required - ทำให้ properties ทั้งหมดเป็น required
interface PartialConfig {
apiUrl?: string;
timeout?: number;
debug?: boolean;
}
type CompleteConfig = Required<PartialConfig>;
// เทียบเท่ากับ:
// {
// apiUrl: string;
// timeout: number;
// debug: boolean;
// }
// Readonly - ทำให้ properties ทั้งหมดเป็น readonly
type ReadonlyUser = Readonly<User>;
// เทียบเท่ากับ:
// {
// readonly id: number;
// readonly name: string;
// readonly email: string;
// readonly age: number;
// readonly address: string;
// }
const user: ReadonlyUser = {
id: 1,
name: 'สมชาย',
email: 'somchai@example.com',
age: 30,
address: 'กรุงเทพ'
};
// user.name = 'ชื่อใหม่'; // Error: Cannot assign to 'name' because it is a read-only property
interface Product {
id: number;
name: string;
description: string;
price: number;
category: string;
stock: number;
createdAt: Date;
updatedAt: Date;
}
// Pick - เลือกเฉพาะ properties ที่ต้องการ
type ProductSummary = Pick<Product, 'id' | 'name' | 'price'>;
// เทียบเท่ากับ:
// {
// id: number;
// name: string;
// price: number;
// }
function displayProductSummary(product: ProductSummary): void {
console.log(`${product.name} - ฿${product.price}`);
}
// Omit - ลบ properties ที่ไม่ต้องการออก
type ProductWithoutTimestamps = Omit<Product, 'createdAt' | 'updatedAt'>;
// เทียบเท่ากับ:
// {
// id: number;
// name: string;
// description: string;
// price: number;
// category: string;
// stock: number;
// }
type CreateProductInput = Omit<Product, 'id' | 'createdAt' | 'updatedAt'>;
// ใช้สำหรับ input เมื่อสร้าง product ใหม่ (ไม่ต้องมี id และ timestamps)
function createProduct(input: CreateProductInput): Product {
return {
...input,
id: Math.floor(Math.random() * 10000),
createdAt: new Date(),
updatedAt: new Date()
};
}
// Record - สร้าง object type ที่มี keys และ values เป็น type ที่กำหนด
type UserRole = 'admin' | 'moderator' | 'user' | 'guest';
interface Permission {
read: boolean;
write: boolean;
delete: boolean;
}
// สร้าง mapping จาก UserRole ไปยัง Permission
type RolePermissions = Record<UserRole, Permission>;
// เทียบเท่ากับ:
// {
// admin: Permission;
// moderator: Permission;
// user: Permission;
// guest: Permission;
// }
const permissions: RolePermissions = {
admin: { read: true, write: true, delete: true },
moderator: { read: true, write: true, delete: false },
user: { read: true, write: false, delete: false },
guest: { read: true, write: false, delete: false }
};
// Record กับ string keys
type ErrorMessages = Record<string, string>;
const errors: ErrorMessages = {
'required': 'ฟิลด์นี้จำเป็นต้องกรอก',
'email': 'รูปแบบอีเมลไม่ถูกต้อง',
'minLength': 'ความยาวต่ำกว่าที่กำหนด',
'maxLength': 'ความยาวเกินที่กำหนด'
};
// Record สำหรับ caching
type Cache<T> = Record<string, T>;
interface UserData {
id: number;
name: string;
email: string;
}
const userCache: Cache<UserData> = {};
userCache['user_1'] = { id: 1, name: 'สมชาย', email: 'somchai@example.com' };
userCache['user_2'] = { id: 2, name: 'สมหญิง', email: 'somying@example.com' };
// ReturnType - ดึง return type ของ function
function calculateTotal(price: number, quantity: number, discount: number = 0): number {
return price * quantity * (1 - discount);
}
type CalculateTotalReturn = ReturnType<typeof calculateTotal>;
// type CalculateTotalReturn = number
function fetchUserData() {
return {
id: 1,
name: 'สมชาย',
email: 'somchai@example.com',
roles: ['user', 'admin']
};
}
type UserData = ReturnType<typeof fetchUserData>;
// type UserData = {
// id: number;
// name: string;
// email: string;
// roles: string[];
// }
// Parameters - ดึง parameters type ของ function
function createUser(name: string, age: number, email: string): User {
return { id: 1, name, age, email, address: '' };
}
type CreateUserParams = Parameters<typeof createUser>;
// type CreateUserParams = [name: string, age: number, email: string]
// ใช้ Parameters สำหรับสร้าง wrapper function
function loggedCreateUser(...args: Parameters<typeof createUser>): User {
console.log('Creating user with params:', args);
return createUser(...args);
}
// Awaited - ดึง type ที่ Promise resolve
type AsyncFunction = () => Promise<{ data: string; success: boolean }>;
type ResolvedType = Awaited<ReturnType<AsyncFunction>>;
// type ResolvedType = { data: string; success: boolean }
async function fetchData(): Promise<{ items: string[]; total: number }> {
return { items: ['a', 'b', 'c'], total: 3 };
}
type FetchDataResult = Awaited<ReturnType<typeof fetchData>>;
// type FetchDataResult = { items: string[]; total: number }
Type Guards ช่วยให้เราสามารถตรวจสอบและแคบ type ลงให้เฉพาะเจาะจงมากขึ้นได้ ทำให้เราสามารถเข้าถึง properties หรือ methods ที่เฉพาะเจาะจงได้อย่างปลอดภัย
// typeof Type Guards
function processValue(value: string | number): string {
if (typeof value === 'string') {
// ใน block นี้ TypeScript รู้ว่า value เป็น string
return value.toUpperCase();
} else {
// ใน block นี้ TypeScript รู้ว่า value เป็น number
return value.toFixed(2);
}
}
// instanceof Type Guards
class Dog {
bark(): void {
console.log('Woof!');
}
}
class Cat {
meow(): void {
console.log('Meow!');
}
}
function makeSound(animal: Dog | Cat): void {
if (animal instanceof Dog) {
animal.bark(); // TypeScript รู้ว่าเป็น Dog
} else {
animal.meow(); // TypeScript รู้ว่าเป็น Cat
}
}
// in operator Type Guards
interface Car {
drive(): void;
wheels: number;
}
interface Boat {
sail(): void;
sails: number;
}
function operate(vehicle: Car | Boat): void {
if ('drive' in vehicle) {
// vehicle เป็น Car
vehicle.drive();
console.log('มี', vehicle.wheels, 'ล้อ');
} else {
// vehicle เป็น Boat
vehicle.sail();
console.log('มี', vehicle.sails, 'ใบเรือ');
}
}
// Custom Type Guard Functions
interface Fish {
swim(): void;
fins: number;
}
interface Bird {
fly(): void;
wings: number;
}
// Type Predicate
function isFish(animal: Fish | Bird): animal is Fish {
return (animal as Fish).swim !== undefined;
}
function move(animal: Fish | Bird): void {
if (isFish(animal)) {
// TypeScript รู้ว่า animal เป็น Fish
animal.swim();
console.log('ปลามี', animal.fins, 'ครีบ');
} else {
// TypeScript รู้ว่า animal เป็น Bird
animal.fly();
console.log('นกมี', animal.wings, 'ปีก');
}
}
// Discriminated Unions
interface Square {
kind: 'square';
size: number;
}
interface Rectangle {
kind: 'rectangle';
width: number;
height: number;
}
interface Circle {
kind: 'circle';
radius: number;
}
type Shape = Square | Rectangle | Circle;
function getArea(shape: Shape): number {
// ใช้ kind property เป็นตัวแบ่งแยก type
switch (shape.kind) {
case 'square':
return shape.size * shape.size;
case 'rectangle':
return shape.width * shape.height;
case 'circle':
return Math.PI * shape.radius ** 2;
}
}
// Exhaustiveness Checking
function assertNever(value: never): never {
throw new Error(`Unexpected value: ${value}`);
}
function getAreaSafe(shape: Shape): number {
switch (shape.kind) {
case 'square':
return shape.size * shape.size;
case 'rectangle':
return shape.width * shape.height;
case 'circle':
return Math.PI * shape.radius ** 2;
default:
// ถ้าเรา cover ทุก case แล้ว shape จะเป็น never
// ถ้ามีการเพิ่ม Shape ใหม่และลืม handle TypeScript จะ error
return assertNever(shape);
}
}
// ใช้ชื่อที่สื่อความหมายและชัดเจน
// ดี
interface UserProfile {
id: number;
name: string;
email: string;
}
// ไม่ดี
interface Data {
a: number;
b: string;
c: string;
}
// Interface ควรเป็นคำนาม Type Alias ก็เช่นกัน
interface Product { }
type OrderStatus = 'pending' | 'approved';
// ใช้ I prefix หรือ T prefix หรือไม่ก็ได้ แต่ควรใช้แบบเดียวกันทั้ง project
interface IUser { } // style 1
type TProduct = { }; // style 1
interface User { } // style 2 (แนะนำ)
type Product = { }; // style 2 (แนะนำ)
// หลีกเลี่ยง any ให้มากที่สุด เพราะจะสูญเสีย type safety
// ไม่ดี
function processData(data: any) {
return data.someProperty; // ไม่มีการตรวจสอบ type
}
// ดีกว่า - ใช้ unknown และทำ type checking
function processDataSafe(data: unknown) {
if (typeof data === 'object' && data !== null && 'someProperty' in data) {
return (data as { someProperty: any }).someProperty;
}
throw new Error('Invalid data format');
}
// never - ใช้สำหรับสิ่งที่ไม่ควรเกิดขึ้น
function throwError(message: string): never {
throw new Error(message);
}
function infiniteLoop(): never {
while (true) {
// loop forever
}
}
// อย่าทำซ้ำ types ที่เหมือนกัน
// ไม่ดี
interface CreateUserRequest {
name: string;
email: string;
password: string;
}
interface UpdateUserRequest {
name: string;
email: string;
}
interface UserResponse {
id: number;
name: string;
email: string;
}
// ดี - ใช้ Utility Types
interface User {
id: number;
name: string;
email: string;
password: string;
}
type CreateUserRequest = Omit<User, 'id'>;
type UpdateUserRequest = Partial<Omit<User, 'id' | 'password'>>;
type UserResponse = Omit<User, 'password'>;
TypeScript Variable Types เป็นเครื่องมือที่ทรงพลังในการสร้างโค้ดที่มีความปลอดภัยและบำรุงรักษาง่ายขึ้น เราได้เรียนรู้ตั้งแต่ basic types, advanced types, การทำงานกับ JavaScript libraries, component types สำหรับ frontend frameworks, Web API types, และ best practices ต่าง ๆ
การใช้ TypeScript อาจดูซับซ้อนในตอนแรก แต่เมื่อเราเข้าใจระบบ type และเครื่องมือต่าง ๆ แล้ว มันจะช่วยให้เราเขียนโค้ดได้เร็วขึ้นและมีข้อผิดพลาดน้อยลง การ type checking ที่ทำในระหว่างการเขียนโค้ดช่วยจับข้อผิดพลาดก่อนที่โค้ดจะถูกรัน และ IDE support ที่ดีขึ้นทำให้เรามี autocomplete และ documentation ที่แม่นยำกว่า
จงฝึกฝนการใช้ TypeScript อย่างสม่ำเสมอ เริ่มจากโปรเจกต์เล็ก ๆ และค่อย ๆ เพิ่มความซับซ้อนขึ้น เมื่อเวลาผ่านไป คุณจะพบว่า TypeScript กลายเป็นเครื่องมือที่ขาดไม่ได้ในการพัฒนา JavaScript applications ที่มีคุณภาพ