TypeScript Best Practices cho React Developers

TypeScript Best Practices cho React Developers
TypeScript đã trở thành standard trong React development. Trong bài viết này, chúng ta sẽ khám phá các best practices giúp bạn viết code TypeScript tốt hơn, maintainable hơn và ít bugs hơn.
Tại sao TypeScript quan trọng?
TypeScript mang lại nhiều lợi ích:
- Type safety: Catch errors lúc compile time thay vì runtime
- Better IDE support: Autocomplete và IntelliSense tuyệt vời
- Refactoring confidence: Dễ dàng refactor code mà không lo break
- Self-documenting code: Types serve như documentation
Best Practice 1: Sử dụng Interface cho Props
❌ Không nên
type ButtonProps = {
text: string;
onClick: any; // Tránh dùng any!
};
✅ Nên làm
interface ButtonProps {
text: string;
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
variant?: "primary" | "secondary" | "outline";
disabled?: boolean;
children?: React.ReactNode;
}
export function Button({ text, onClick, variant = "primary", disabled = false }: ButtonProps) {
return (
<button
className={`btn btn-${variant}`}
onClick={onClick}
disabled={disabled}
>
{text}
</button>
);
}
Tại sao?
- Interface có thể extend và merge dễ dàng
- Naming convention rõ ràng:
ComponentNameProps
- Optional properties với
?
- Default values trong destructuring
Best Practice 2: Generic Components
Generic components giúp tạo reusable components với type safety.
Example: Generic List Component
interface ListProps<T> {
items: T[];
renderItem: (item: T, index: number) => React.ReactNode;
keyExtractor: (item: T) => string | number;
emptyMessage?: string;
}
function List<T>({ items, renderItem, keyExtractor, emptyMessage = "No items" }: ListProps<T>) {
if (items.length === 0) {
return <p>{emptyMessage}</p>;
}
return (
<ul>
{items.map((item, index) => (
<li key={keyExtractor(item)}>
{renderItem(item, index)}
</li>
))}
</ul>
);
}
// Usage
interface User {
id: number;
name: string;
email: string;
}
function UserList({ users }: { users: User[] }) {
return (
<List<User>
items={users}
renderItem={(user) => (
<div>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
)}
keyExtractor={(user) => user.id}
emptyMessage="Chưa có users nào"
/>
);
}
Best Practice 3: Union Types cho Props Variants
Sử dụng union types để enforce correct prop combinations.
Example: Button với Multiple Variants
type BaseButtonProps = {
children: React.ReactNode;
disabled?: boolean;
className?: string;
};
type PrimaryButtonProps = BaseButtonProps & {
variant: "primary";
onClick: () => void;
};
type LinkButtonProps = BaseButtonProps & {
variant: "link";
href: string;
target?: "_blank" | "_self";
};
type SubmitButtonProps = BaseButtonProps & {
variant: "submit";
form: string;
};
type ButtonProps = PrimaryButtonProps | LinkButtonProps | SubmitButtonProps;
function Button(props: ButtonProps) {
const { variant, children, disabled, className } = props;
if (variant === "link") {
return (
<a
href={props.href}
target={props.target}
className={className}
>
{children}
</a>
);
}
if (variant === "submit") {
return (
<button
type="submit"
form={props.form}
disabled={disabled}
className={className}
>
{children}
</button>
);
}
return (
<button
onClick={props.onClick}
disabled={disabled}
className={className}
>
{children}
</button>
);
}
TypeScript sẽ enforce đúng props cho từng variant!
Best Practice 4: Utility Types
Tận dụng built-in utility types của TypeScript.
Partial<T>
interface User {
id: number;
name: string;
email: string;
bio: string;
}
// Update user - không cần tất cả fields
function updateUser(id: number, updates: Partial<User>) {
// API call to update user
}
updateUser(1, { name: "New Name" }); // ✅ OK
updateUser(1, { email: "new@email.com", bio: "New bio" }); // ✅ OK
Pick<T, K>
và Omit<T, K>
// Chỉ lấy một số fields
type UserPreview = Pick<User, "id" | "name">;
// Loại bỏ một số fields
type UserWithoutId = Omit<User, "id">;
// Usage in components
interface UserCardProps {
user: UserPreview; // Chỉ cần id và name
}
Record<K, T>
// Map từ string key đến value type
type UserRole = "admin" | "editor" | "viewer";
const permissions: Record<UserRole, string[]> = {
admin: ["read", "write", "delete"],
editor: ["read", "write"],
viewer: ["read"],
};
Best Practice 5: Custom Hooks với TypeScript
Type your custom hooks properly.
import { useState, useEffect } from 'react';
interface UseFetchResult<T> {
data: T | null;
loading: boolean;
error: Error | 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<Error | null>(null);
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
setData(result);
setError(null);
} catch (e) {
setError(e instanceof Error ? e : new Error('Unknown error'));
setData(null);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, [url]);
return { data, loading, error, refetch: fetchData };
}
// Usage
interface User {
id: number;
name: string;
}
function UserProfile({ userId }: { userId: number }) {
const { data: user, loading, error } = useFetch<User>(`/api/users/${userId}`);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!user) return null;
return <div>{user.name}</div>;
}
Best Practice 6: Event Handlers
Type event handlers correctly cho better type safety.
interface FormProps {
onSubmit: (data: FormData) => void;
}
function MyForm({ onSubmit }: FormProps) {
// Form submit handler
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
onSubmit(formData);
};
// Input change handler
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
};
// Button click handler
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
console.log('Button clicked', e.currentTarget);
};
return (
<form onSubmit={handleSubmit}>
<input type="text" onChange={handleChange} />
<button onClick={handleClick}>Submit</button>
</form>
);
}
Best Practice 7: Avoid Type Assertions
❌ Không nên
const value = JSON.parse(jsonString) as User; // Unsafe!
✅ Nên làm
import { z } from 'zod';
// Define schema với Zod
const UserSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
});
type User = z.infer<typeof UserSchema>;
function parseUser(jsonString: string): User {
const parsed = JSON.parse(jsonString);
return UserSchema.parse(parsed); // Runtime validation!
}
Best Practice 8: Discriminated Unions
Sử dụng discriminated unions cho complex state management.
type RequestState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: string };
function DataComponent() {
const [state, setState] = useState<RequestState<User>>({ status: 'idle' });
// TypeScript biết chính xác properties available cho mỗi status
if (state.status === 'loading') {
return <div>Loading...</div>;
}
if (state.status === 'error') {
return <div>Error: {state.error}</div>; // TypeScript knows 'error' exists
}
if (state.status === 'success') {
return <div>{state.data.name}</div>; // TypeScript knows 'data' exists
}
return <button onClick={() => setState({ status: 'loading' })}>Load Data</button>;
}
Kết luận
TypeScript best practices giúp bạn:
✅ Tránh bugs: Type safety catch errors sớm
✅ Code maintainable hơn: Self-documenting code
✅ Developer experience tốt hơn: Autocomplete và IntelliSense
✅ Refactoring dễ dàng: Confidence khi thay đổi code
Key Takeaways
- Sử dụng Interface cho component props
- Leverage generic types cho reusable components
- Sử dụng union types cho variant props
- Tận dụng utility types (Partial, Pick, Omit, Record)
- Type custom hooks properly
- Type event handlers correctly
- Avoid type assertions, use validation instead
- Sử dụng discriminated unions cho complex state
Next Steps
- Practice các patterns này trong projects của bạn
- Explore thêm advanced TypeScript features
- Check out TypeScript Handbook
Happy coding! 🚀

Cong Dinh
Technology Consultant | Trainer | Solution Architect
Với hơn 10 năm kinh nghiệm trong phát triển web và cloud architecture, tôi giúp doanh nghiệp xây dựng giải pháp công nghệ hiện đại và bền vững. Chuyên môn: Next.js, TypeScript, AWS, và Solution Architecture.
Bài viết liên quan
TypeScript Best Practices 2025: Viết Code Sạch và An toàn
Khám phá các pattern TypeScript hiện đại, utility types, và best practices để viết code type-safe và maintainable, giúp team phát triển hiệu quả hơn.
Bắt đầu với Next.js 15: Hướng dẫn Toàn diện
Khám phá các tính năng mới của Next.js 15 và cách bắt đầu xây dựng ứng dụng hiện đại với App Router, Server Components và Server Actions để tối ưu performance.

Tailwind CSS Tips và Tricks để Tăng Productivity
Khám phá các tips, tricks và best practices khi sử dụng Tailwind CSS để xây dựng UI nhanh hơn và maintainable hơn trong Next.js projects