Nếu bạn là một lập trình viên React và vẫn đang dùng JavaScript, thì bạn đang tự làm khó mình đấy.
Thời gian bạn dành ra để học TypeScript chắc chắn sẽ được “hoàn vốn” bằng thời gian tiết kiệm được khi debug những lỗi kỳ quặc.
Vì vậy, đây là một hướng dẫn giúp bạn chuyển sang TypeScript.
Chỉ những thứ thiết yếu. Không vòng vo 😉. Hãy cùng bắt đầu nhé!
Cách TypeScript hoạt động
TypeScript bổ sung cú pháp cho JavaScript để giúp phát hiện lỗi sớm — trước khi chúng làm crash ứng dụng của bạn.
Cuối cùng thì nó vẫn được biên dịch ra JavaScript.
Phần hay nhất? Bạn có thể từng bước chuyển sang TypeScript.
Tại sao bạn nên chuyển sang TypeScript càng sớm càng tốt
Dưới đây là lý do tại sao mình nghĩ việc này là “không cần suy nghĩ nhiều”:
Lợi ích #1: Bắt lỗi trước khi lên production
Đoạn code JavaScript này nhìn có vẻ ổn… cho đến khi nó khiến bạn phát rồ:
function printFirstChar(users, userIndex) { const name = users[userIndex]; console.log(name[0]);
} const users = ['Fatou', 'Bob']; printFirstChar(users, 5); // ❌ Runtime error: Cannot read properties of undefined
Còn đây là phiên bản TypeScript:
function printFirstChar(users: string[], userIndex: number) { const name = users[userIndex]; if (name != null) { console.log(name[0]); }
}
TypeScript không sửa logic cho bạn, nhưng nó sẽ cảnh báo sớm kiểu như “này, cái này có thể là undefined đó”.
Lợi ích #2: Tự động gợi ý khi dùng thư viện
Đặc biệt hữu ích khi bạn đang khám phá một thư viện mới.
Nếu thư viện đó có định nghĩa kiểu TypeScript, bạn sẽ được gợi ý tự động ngay trong hầu hết các editor.
Lợi ích #3: Làm cho code vững chắc hơn khi có thay đổi
Khi bạn định kiểu đúng cách, TypeScript giống như một chiếc lưới an toàn.
Ví dụ: Nếu không dùng TypeScript, đoạn code này có thể bị lỗi mà bạn không phát hiện:
function getLabel(status) { switch (status) { case 'idle': return 'Idle'; case 'loading': return 'Loading'; case 'success': return 'Success'; }
} getLabel('loading'); // ✅ works // Months later... getLabel('error'); // ❌ returns undefined, but no JS error
Với TypeScript, bạn phải xử lý mọi thay đổi.
type Status = 'idle' | 'loading' | 'success'; function getLabel(status: Status): string { switch (status) { case 'idle': return 'Idle'; case 'loading': return 'Loading'; case 'success': return 'Success'; case 'error': return 'Error'; }
} // Months later...
type Status = 'idle' | 'loading' | 'success' | 'error'; // added 'error' // ✅ TypeScript will show an error for the `getLabel` function: "Not all code paths return a value"
Nếu bạn quên xử lý một case, TypeScript sẽ “la làng”. Và đó chính là điều bạn muốn 😄.
Các khái niệm TypeScript cần biết khi dùng với React
Hãy bắt đầu với những khái niệm tối thiểu cần thiết để làm việc.
✅ Kiểu dữ liệu (Types)
Kiểu dữ liệu cho phép bạn mô tả hình dạng (shape) của dữ liệu và ép buộc nó theo đúng định nghĩa.
Trong TypeScript có kiểu dữ liệu gốc (native types) và kiểu do người dùng định nghĩa.
1. Kiểu gốc (Native types)
Đây là các kiểu được cung cấp sẵn trong TypeScript:
const name = 'Ada'; // string
const age: number = 31; // number
const isActive = true; // boolean
const scores: number[] = [90, 85, 100]; // number[]
Trong một số ví dụ trên, mình khai báo kiểu dữ liệu một cách tường minh (như const age: number = 31
).
Bạn không cần phải làm vậy trong thực tế — TypeScript có thể suy luận kiểu dữ liệu dựa vào giá trị gán.
2. Kiểu tùy chỉnh (Custom types)
Bạn có thể tạo kiểu mới bằng cách kết hợp các kiểu gốc:
type User = { id: number; name: string;
}; const user: User = { id: 1, name: 'Fatou',
};
❌ Tránh dùng any
Trừ khi bạn đang trong quá trình chuyển đổi dự án cũ sang TS, đừng dùng any.
Về cơ bản, any
là JavaScript trá hình.
Thay vào đó, hãy dùng unknown
— nó bắt buộc bạn phải kiểm tra kiểu trước khi sử dụng.
// ❌ Bad: no warning, crashes at runtime
function logLength(value: any) { console.log(value.length);
} logLength(123); // ✅ Safer
function logLength(value: unknown) { if (typeof value === 'string' || Array.isArray(value)) { console.log(value.length); }
} // ✅✅ Better: use an explicit type
function logLength(value: string | ArrayLike<unknown>){ console.log(value.length);
}
🧩 Union & Intersection types
Union cho phép một giá trị là một trong nhiều kiểu:
type Status = 'idle' | 'loading' | 'error'; let currentStatus: Status = 'idle';
Intersection kết hợp nhiều kiểu thành một kiểu duy nhất:
type Name = { name: string };
type Age = { age: number }; type Person = Name & Age; const person: Person = { name: 'Ada', age: 30 };
🧱 Interfaces
Interface định nghĩa cấu trúc của một đối tượng:
interface User { name: string; age: number;
}
Bạn cũng có thể kế thừa interface:
interface Admin extends User { role: string;
}
Nên dùng type hay interface? → Không quá quan trọng.
🔁 Generics
Generics cho phép bạn tạo các kiểu có thể tái sử dụng và linh hoạt.
Ví dụ: bạn đang định nghĩa phản hồi từ một API.
Không dùng generics:
type UserResponse = { status: number; data: User; error?: string;
}; type ProductResponse = { status: number; data: Product; error?: string;
};
=> Quá nhiều lặp lại.
Dùng generics:
type ApiResponse<T> = { status: number; data: T; error?: string;
}; type UserResponse = ApiResponse<User>;
type ProductResponse = ApiResponse<Product>;
Bạn cũng có thể dùng generics trong function:
function wrap<T>(value: T): T[] { return [value];
} wrap(42); // type: number[]
wrap('hi'); // type: string[]
Mình thường nghĩ Generics giống như tham số của hàm — nhưng dành cho kiểu dữ liệu 😉
🔧 Kiểu cho hàm (Function types)
Bạn có thể định kiểu cho tham số và giá trị trả về của hàm.
Bạn phải định kiểu cho tham số, nhưng giá trị trả về có thể để TypeScript tự suy luận.
// This works
function add(a: number, b: number): number { return a + b;
} // This also works: you don't need to type the returned value
function add(a: number, b: number) { return a + b; // inferred as number
}
Bạn cũng có thể định kiểu cho biến hàm:
const add: (a: number, b: number) => number = (a, b) => a + b;
Tuỳ từng tình huống, bạn có thể cần định kiểu cho return type. Khi không chắc chắn, hãy định kiểu cho giá trị trả về.
Cách sử dụng TypeScript với React
🔸 Props
Props chỉ đơn giản là các đối tượng. Bạn có thể định kiểu cho chúng như bất kỳ object nào khác.
Dùng type
:
type GreetingProps = { name: string;
}; function Greeting({ name }: GreetingProps) { return <p>Hello {name}</p>;
}
Dùng interface
:
interface GreetingProps { name: string;
} function Greeting({ name }: GreetingProps) { return <p>Hello {name}</p>;
}
🔸 State
useState
chấp nhận kiểu generic, nhưng TypeScript thường có thể suy luận kiểu một cách tự động:
const [count, setCount] = useState(0); // inferred as number
Nhưng nếu TS không thể đoán được kiểu, bạn nên khai báo rõ ràng:
const [user, setUser] = useState<{ id: number; name: string } | null>(null);
🔸 Biến và hàm trong component
Giống như viết TypeScript bình thường:
const users: User[] = [{name: "Fatou", age: 31}, {name: "Bob", age: 25} ];
const age = 30; // inferred as number
Khi bạn viết các hàm xử lý sự kiện như onClick, hãy di chuột lên phần tử DOM để xem kiểu phù hợp:
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => { console.log('Clicked!', event);
}; // Or:
const handleClick: React.MouseEventHandler<HTMLButtonElement> = (event) => { console.log('Clicked!', event);
};
🔸 Tái sử dụng kiểu dữ liệu giữa các components
Bạn có thể mở rộng hoặc kết hợp các type
hoặc interface
để tránh lặp lại.
Dùng interface
:
interface ButtonProps { label: string;
} interface IconButtonProps extends ButtonProps { icon: React.ReactNode;
}
Dùng union
với type
:
type ButtonProps = { label: string;
}; type IconButtonProps = ButtonProps & { icon: React.ReactNode;
};
🔸 Hooks
Hooks chỉ là các hàm, nên bạn có thể áp dụng các kỹ thuật TypeScript giống như với hàm thông thường.
1. Hooks có sẵn trong React:
const [count, setCount] = useState<number>(0);
// or let TS infer:
const [count, setCount] = useState(0); const inputRef = useRef<HTMLInputElement>(null); const [state, dispatch] = useReducer( (state: number, action: 'inc' | 'dec') => { switch (action) { case 'inc': return state + 1; case 'dec': return state - 1; } }, 0
);
2. Hooks tùy chỉnh (Custom hooks)
function useToggle(initial: boolean): [boolean, () => void] { const [state, setState] = useState(initial); const toggle = () => setState((prev) => !prev); return [state, toggle];
} const [isOpen, toggleOpen] = useToggle(false);
📚 Tài nguyên học TypeScript chuyên sâu
Nếu bạn muốn học sâu hơn về TypeScript, dưới đây là một số nguồn cực kỳ hữu ích:
🧠 TypeScript Official Docs: Tài liệu chính thức từ TS. Rất rõ ràng, có cấu trúc tốt, nhiều ví dụ, và có playground để thử nghiệm.
🎯 Total TypeScript (của Matt Pocock): Một trang cực kỳ hay, đặc biệt cho lập trình viên React. Có khóa học miễn phí, ví dụ thực tế và các pattern nâng cao.
✨ TypeScript Style Guide: Hướng dẫn ngắn gọn, thực tế để viết code TypeScript sạch và đồng nhất. Rất hữu ích khi bạn bắt đầu build app thực tế.
🧩 Type Challenges: Tập hợp các bài tập TypeScript từ dễ đến “căng não”. Rất phù hợp với những ai học bằng cách giải bài và muốn nâng cấp kỹ năng type system.
📘 Effective TypeScript (sách + blog của Dan Vanderkam): Dành cho người đã có nền tảng TS. Dạy cách viết code TS tốt hơn và tránh lỗi thường gặp.
⚛️ React TypeScript Cheatsheets: Là bí kíp “gối đầu giường” cho dev React. Bao phủ từ components, context, đến hooks. Rất dễ tiếp cận và tiện tra cứu nhanh.
Kết luận
Sử dụng TypeScript với React là một trong những nâng cấp đáng giá nhất mà bạn có thể thực hiện.
Nó giúp bạn phát hiện lỗi sớm, có công cụ hỗ trợ lập trình tốt hơn, và khiến code dễ bảo trì hơn.
Điều tuyệt vời nhất? Bạn không cần học tất cả mọi thứ ngay từ đầu. Hãy bắt đầu với những nền tảng cơ bản như:
→ types
, interfaces
, generics
— và dần dần phát triển thêm.
Có rất nhiều ví dụ trên mạng nếu bạn gặp khó khăn, và các công cụ AI cũng có thể giúp bạn nhanh chóng vượt qua các trở ngại.
Nếu bạn có bất kỳ câu hỏi nào, cứ hỏi mình nhé 🙂.