Quản lý trạng thái thông minh trong React với TypeScript
Quản lý trạng thái trong React trở nên dễ dự đoán và an toàn hơn khi bạn kết hợp sức mạnh của hệ thống kiểu của TypeScript với các tính năng không thay đổi như readonly. Trong bài viết này, chúng ta sẽ khám phá cách định kiểu mạnh cho trạng thái của bạn bằng useState(), đảm bảo tính bất biến bằng cách sử dụng readonly, và thậm chí đi sâu hơn với ReadonlyDeep cho các cấu trúc phức tạp. Những mẫu này không chỉ cải thiện IntelliSense và ngăn ngừa lỗi mà còn phù hợp hoàn hảo với triết lý render của React.
🧠 useState<Type>() trong TypeScript
- Định kiểu mạnh cho các biến trạng thái giúp an toàn hơn và cải thiện IntelliSense.
- Ngăn chặn sự không tương thích kiểu trong quá trình cập nhật trạng thái.
- Khuyến khích quản lý trạng thái dễ dự đoán và bảo trì hơn.
typescript
const [count, setCount] = useState<number>(0); // Được định kiểu mạnh là number
🔒 readonly trong TypeScript
- Đảm bảo tính bất biến cho các đối tượng và mảng.
- Ngăn chặn việc gán lại hoặc thay đổi thuộc tính.
- Hữu ích để bảo vệ cấu trúc trạng thái khỏi những thay đổi không mong muốn.
Từng thuộc tính readonly (thủ công)
typescript
type User = {
readonly id: number;
readonly name: string;
};
const [user, setUser] = useState<User>({
id: 1,
name: 'Arka',
});
// user.id = 2; ❌ Lỗi: Không thể gán giá trị cho 'id' vì nó là thuộc tính chỉ đọc
Sử dụng Readonly trực tiếp (tự động)
typescript
type User = {
id: number;
name: string;
};
const [user, setUser] = useState<Readonly<User>>({
id: 1,
name: 'Arka',
});
// user.id = 2; ❌ Lỗi: Không thể gán giá trị cho 'id' vì nó là thuộc tính chỉ đọc
// Cập nhật trạng thái một cách an toàn
const updateName = (newName: string) => {
setUser({ ...user, name: newName }); // ✅ Cập nhật không thay đổi
};
📦 readonly so với ReadonlyDeep
readonlychỉ áp dụng cho các thuộc tính cấp cao nhất.- Để có tính bất biến sâu, hãy sử dụng các loại tiện ích như
ReadonlyDeep<T>(thông qua thư viện hoặc các loại tùy chỉnh). - Trạng thái React thường hưởng lợi từ tính bất biến nông, nhưng tính bất biến sâu có thể giúp trong các cấu trúc phức tạp.
Cài đặt type-fest
bash
npm install type-fest
Ví dụ React + TypeScript với ReadonlyDeep
typescript
import { useState } from 'react';
import type { ReadonlyDeep } from 'type-fest';
type Address = {
city: string;
zip: number;
};
type User = {
id: number;
name: string;
address: Address;
};
function Profile() {
const [user, setUser] = useState<ReadonlyDeep<User>>({
id: 1,
name: 'Arka',
address: {
city: 'Bangalore',
zip: 560001,
},
});
// ❌ Tất cả sẽ gây lỗi do readonly sâu
// user.name = 'New Name';
// user.address.city = 'Mumbai';
const updateCity = (newCity: string) => {
setUser({
...user,
address: {
...user.address,
city: newCity,
},
});
};
return (
<div>
<p>{user.name} sống ở {user.address.city}</p>
<button onClick={() => updateCity('Mumbai')}>Chuyển đến Mumbai</button>
</div>
);
}
⚠️ Tại sao readonly hữu ích cho đối tượng và mảng
- Làm cho toàn bộ đối tượng hoặc mảng không thay đổi, không chỉ các khóa đơn lẻ.
- Ngăn chặn sự thay đổi trực tiếp, điều này có thể phá vỡ chu kỳ render của React.
- Đặc biệt quan trọng khi truyền props hoặc quản lý trạng thái chia sẻ.
typescript
const numbers: readonly number[] = [1, 2, 3];
// numbers.push(4); ❌ Lỗi: push không tồn tại trên mảng readonly
🚫 Tránh thay đổi trực tiếp trạng thái
- Luôn sử dụng hàm setter từ
useState. - Thay đổi trực tiếp có thể dẫn đến render lỗi thời hoặc hành vi không mong muốn.
readonlygiúp thực thi mẫu này và phát hiện lỗi sớm.
typescript
import { useState } from 'react';
function TodoList() {
const [todos, setTodos] = useState<string[]>(['Học TS']);
const addTodo = (todo: string) => {
// ❌ Thay đổi trực tiếp (không tốt)
// todos.push(todo);
// ✅ Cách đúng
setTodos([...todos, todo]);
};
return <button onClick={() => addTodo('Viết blog')}>Thêm Todo</button>;
}
🧩 Mảng readonly trong React
- Ngăn chặn việc sử dụng các phương thức thay đổi như
push,pop,splice, v.v. - Khuyến khích sử dụng các mẫu không thay đổi như
map,filter,concat. - Phù hợp với kỳ vọng của React về tính bất biến cho việc render lại.
✅ Thực hành tốt nhất
- Kết hợp
useStatevới kiểu định nghĩa phù hợp để cập nhật an toàn hơn. - Sử dụng
readonlytrong các loại để bảo vệ cấu trúc dữ liệu. - Tránh thay đổi để duy trì hành vi render dễ dự đoán của React.
⚠️ Các cạm bẫy thường gặp
- Không hiểu rõ về tính bất biến có thể dẫn đến lỗi không mong muốn.
- Thường xuyên kiểm tra các thuộc tính
readonlyđể tránh thay đổi sai.
⚡ Mẹo hiệu suất
- Sử dụng cấu trúc dữ liệu không thay đổi giúp giảm thiểu render không cần thiết.
- Theo dõi trạng thái cẩn thận để tối ưu hóa hiệu suất ứng dụng.
❓ Câu hỏi thường gặp
1. Tại sao nên sử dụng readonly trong TypeScript?
readonly giúp bảo vệ dữ liệu khỏi thay đổi không mong muốn, làm cho mã nguồn an toàn hơn và dễ bảo trì.
2. Làm thế nào để cập nhật trạng thái một cách an toàn trong React?
Luôn sử dụng hàm setter từ useState và tránh thay đổi trực tiếp trạng thái.
3. Có những công cụ nào hỗ trợ readonly trong TypeScript?
Thư viện type-fest cung cấp nhiều loại tiện ích hỗ trợ tính bất biến sâu và các loại tương tự.
Kết thúc bài viết, việc kết hợp TypeScript với React sẽ giúp bạn quản lý trạng thái một cách thông minh hơn, giảm thiểu lỗi và cải thiện khả năng bảo trì mã nguồn. Nếu bạn thấy bài viết này hữu ích, hãy chia sẻ với cộng đồng phát triển của bạn!