Vượt xa mớ hỗn độn của việc Fetching thủ công
Quay lại năm 2018, tôi đã dành hàng tuần để viết các logic lặp đi lặp lại cho mọi lệnh gọi API. Tôi thường khởi tạo ba mảng state cho mỗi lần fetch: data, loading và error. Các component của tôi trở nên cồng kềnh với các hook useEffect – một cơn ác mộng khi debug. Bất kỳ ai từng cố gắng đồng bộ hóa dữ liệu giữa các component chỉ với useState và useContext đều biết rằng cấu trúc đó sẽ đổ vỡ ngay khi ứng dụng của bạn phát triển.
Chúng ta thường mắc sai lầm khi coi Server State giống như Client State. Client state mang tính cục bộ; bạn sở hữu nó hoàn toàn, giống như nút gạt sidebar hoặc một ô nhập văn bản. Server state là dữ liệu từ xa. Bạn không sở hữu nó—bạn chỉ có một bản chụp (snapshot) có thể bị cũ (stale) chỉ trong vài mili giây. Các công cụ truyền thống như Redux buộc bạn phải quản lý vòng đời này một cách thủ công. Bạn bị mắc kẹt trong việc viết code cho caching, invalidation và re-fetching lặp đi lặp lại.
TanStack Query (trước đây là React Query) đóng vai trò như một trình quản lý state bất đồng bộ. Nó tự động xử lý cache và quản lý các trạng thái loading. Điều này đảm bảo giao diện người dùng (UI) của bạn luôn đồng bộ với backend mà không cần hàng trăm dòng code boilerplate. Nó cho phép bạn coi các lệnh gọi API như các dependency mang tính khai báo (declarative) thay vì các side effect hỗn loạn.
Setup: Chuẩn bị thư viện
Đầu tiên, hãy đưa thư viện vào dự án của bạn. TanStack Query không phụ thuộc vào framework, nhưng chúng ta sẽ tập trung vào package dành cho React ở đây. Tôi thực sự khuyên bạn nên cài đặt DevTools. Chúng là cứu cánh khi bạn cần xem chính xác những gì đang nằm trong cache tại bất kỳ thời điểm nào.
# Sử dụng npm
npm install @tanstack/react-query @tanstack/react-query-devtools
# Sử dụng yarn
yarn add @tanstack/react-query @tanstack/react-query-devtools
# Sử dụng pnpm
pnpm add @tanstack/react-query @tanstack/react-query-devtools
Sau khi cài đặt, hãy bọc ứng dụng của bạn bằng QueryClientProvider. Điều này tạo ra context cho phép mọi component trong cây thư mục của bạn giao tiếp với query cache.
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
{/* Các component của ứng dụng */}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
Triển khai: Dữ liệu sạch hơn, nhanh hơn
Sau khi hoàn tất cài đặt, chúng ta có thể sử dụng hook useQuery. Đây là nơi phép màu xảy ra. Trong một dự án gần đây với hơn 20 endpoint API, việc chuyển sang mô hình này đã giúp chúng tôi xóa khoảng 1.200 dòng logic state dư thừa.
Fetch dữ liệu với useQuery
Hãy xem cách triển khai này cho danh sách dự án. Lưu ý cách component được giữ sạch sẽ khi chúng ta loại bỏ useEffect để lấy dữ liệu.
import { useQuery } from '@tanstack/react-query';
const fetchProjects = async () => {
const response = await fetch('/api/projects');
if (!response.ok) throw new Error('Phản hồi mạng không ổn định');
return response.json();
};
function ProjectList() {
const { data, isLoading, isError, error } = useQuery({
queryKey: ['projects'],
queryFn: fetchProjects,
staleTime: 1000 * 60 * 5, // 5 phút
});
if (isLoading) return <div>Đang tải các dự án...</div>;
if (isError) return <div>Lỗi: {error.message}</div>;
return (
<ul>
{data.map((project) => (
<li key={project.id}>{project.name}</li>
))}
</ul>
);
}
Sai lầm số 1: staleTime và gcTime
Lỗi phổ biến nhất mà tôi thấy là bỏ qua staleTime. Theo mặc định, nó được đặt là 0. Điều này có nghĩa là mỗi khi người dùng chuyển tab và quay lại, một lần fetch ngầm sẽ được kích hoạt. Mặc dù dữ liệu mới là tốt, nhưng nó có thể gây áp lực không cần thiết lên server của bạn.
- staleTime: Khoảng thời gian dữ liệu được coi là “mới” (fresh). Dữ liệu mới sẽ được lấy từ cache mà không cần yêu cầu mạng.
- gcTime (trước đây là cacheTime): Khoảng thời gian dữ liệu không hoạt động nằm trong bộ nhớ trước khi bị dọn dẹp (garbage collected).
Trong một ứng dụng thực tế với 50.000 người dùng hàng tháng, chúng tôi đã đặt staleTime toàn cục là 60 giây. Thay đổi đơn giản này đã giảm tải server xuống 45% mà không ảnh hưởng đến trải nghiệm người dùng.
Xử lý Mutation và Invalidation
Fetching chỉ là một nửa công việc. Bạn cũng cần gửi dữ liệu đi. Hook useMutation xử lý việc này một cách hoàn hảo. Bí quyết nằm ở Query Invalidation: sau khi thêm một dự án, bạn muốn danh sách tự động cập nhật lại.
import { useMutation, useQueryClient } from '@tanstack/react-query';
function AddProjectForm() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (newProject) => {
return fetch('/api/projects', {
method: 'POST',
body: JSON.stringify(newProject),
});
},
onSuccess: () => {
// Đánh dấu ngay lập tức 'projects' là cũ (stale) để kích hoạt làm mới
queryClient.invalidateQueries({ queryKey: ['projects'] });
},
});
const handleSubmit = (event) => {
event.preventDefault();
mutation.mutate({ name: 'Dự án mới tuyệt vời' });
};
return (
<form onSubmit={handleSubmit}>
<button type="submit" disabled={mutation.isPending}>
{mutation.isPending ? 'Đang lưu...' : 'Thêm dự án'}
</button>
</form>
);
}
Kiểm tra chuyên nghiệp: Giám sát và Độ tin cậy
Khi các query của bạn đã được kết nối, hãy sử dụng DevTools để xác minh hành vi. Đó là cách duy nhất để chắc chắn 100% rằng cache của bạn không thực hiện điều gì bất ngờ ở phía sau.
Hãy ghi nhớ bốn bước kiểm tra sau:
- Key phân cấp (Hierarchical Keys): Sử dụng
['projects', id]thay vì các chuỗi phẳng. Điều này cho phép bạn nhắm mục tiêu vào dữ liệu cụ thể để invalidate trong khi vẫn giữ nguyên phần còn lại của cache. - Logic Retry: TanStack Query thử lại các yêu cầu thất bại 3 lần theo mặc định. Đối với các thành phần UI quan trọng, tôi thường giảm xuống còn 1 lần thử lại để người dùng không phải chờ đợi vòng xoay (spinner) trong 10 giây.
- Window Focus: Theo dõi DevTools khi bạn chuyển đổi tab trình duyệt. Nếu dữ liệu của bạn là tĩnh, hãy tắt
refetchOnWindowFocusđể tiết kiệm băng thông. - Error Boundaries: Sử dụng tùy chọn
throwOnErrorđể bắt các lỗi API ở cấp cao hơn trong cây component. Nó giúp các component riêng lẻ của bạn sạch sẽ hơn nhiều.
Quản lý server state không nên là một nỗi đau đầu. Bằng cách ủy thác công việc nặng nhọc cho TanStack Query, bạn ngừng đối đầu với các race condition và bắt đầu tập trung xây dựng tính năng. Sự thay đổi kiến trúc này tạo ra các ứng dụng linh hoạt hơn và một đội ngũ phát triển hạnh phúc hơn nhiều.

