Lộ trình React: React Query vs SWR – Chọn Lựa Thư Viện Data Fetching Phù Hợp

Chào mừng bạn quay trở lại với series React Roadmap! Chúng ta đã cùng nhau đi qua rất nhiều kiến thức nền tảng quan trọng, từ việc hiểu React là gì, cách hoạt động của JSX, sự khác biệt giữa Class và Functional Components, cách quản lý dữ liệu với Props và State, cho đến sức mạnh của Hooks như useState và useEffect, và thậm chí là các giải pháp quản lý state phức tạp hơn. Một phần không thể thiếu của hầu hết các ứng dụng web hiện đại chính là việc tương tác với server để lấy dữ liệu (data fetching) và gửi dữ liệu đi (mutations).

Ở bài viết trước, chúng ta đã tìm hiểu cách cơ bản để gọi API sử dụng Fetch API hoặc Axios. Tuy nhiên, khi ứng dụng của bạn lớn dần, việc quản lý các trạng thái khi gọi API (đang tải, lỗi, thành công), xử lý việc caching dữ liệu, cập nhật lại dữ liệu khi cần (revalidation), hay xử lý các yêu cầu mutation (POST, PUT, DELETE)… trở nên cực kỳ phức tạp nếu chỉ dựa vào `useState` và `useEffect` thuần túy. Đó là lúc các thư viện quản lý data fetching chuyên biệt phát huy sức mạnh. Trong bài viết này, chúng ta sẽ cùng nhau khám phá hai trong số những thư viện phổ biến và mạnh mẽ nhất hiện nay trong hệ sinh thái React: React Query (nay là TanStack Query) và SWR.

Cả hai thư viện này đều cung cấp các giải pháp tuyệt vời để đơn giản hóa và tối ưu hóa quy trình làm việc với dữ liệu bất đồng bộ trong ứng dụng React của bạn. Nhưng làm thế nào để biết đâu là lựa chọn phù hợp cho dự án của bạn? Hãy cùng đi sâu vào tìm hiểu nhé!

Tại Sao Cần Thư Viện Data Fetching Chuyên Biệt?

Như đã đề cập, việc quản lý data fetching thủ công với `useEffect` và `useState` có thể nhanh chóng trở thành một cơn ác mộng khi ứng dụng của bạn phát triển. Dưới đây là một số vấn đề phổ biến bạn sẽ gặp phải:

  • Quản lý trạng thái tải (Loading states): Bạn cần state để theo dõi xem dữ liệu đang được tải hay không.
  • Quản lý trạng thái lỗi (Error states): Bạn cần state để lưu trữ thông báo lỗi nếu có.
  • Quản lý dữ liệu (Data states): Bạn cần state để lưu trữ dữ liệu khi tải xong.
  • Caching: Làm thế nào để lưu trữ dữ liệu đã tải để sử dụng lại mà không cần gọi API liên tục? Làm thế nào để dữ liệu cached không bị cũ?
  • Revalidation (Xác thực lại): Làm thế nào để tự động cập nhật dữ liệu trên UI khi dữ liệu trên server thay đổi, hoặc khi người dùng tập trung lại vào cửa sổ trình duyệt, hoặc khi kết nối mạng được phục hồi?
  • Pagination và Infinite Scroll: Xử lý việc tải dữ liệu theo trang hoặc tải thêm dữ liệu khi cuộn trang một cách hiệu quả.
  • Mutations: Cập nhật dữ liệu trên server và đồng bộ dữ liệu trên UI một cách mượt mà (optimistic updates, invalidation).
  • Race Conditions: Xử lý các trường hợp khi nhiều yêu cầu API được gửi đi và trả về không theo thứ tự, dẫn đến hiển thị sai dữ liệu.
  • Performance: Tránh các request không cần thiết, tối ưu tốc độ hiển thị dữ liệu.

React Query và SWR được thiết kế để giải quyết những vấn đề này một cách có hệ thống, giúp bạn viết code sạch hơn, dễ bảo trì hơn và cải thiện trải nghiệm người dùng.

React Query (TanStack Query): “Server State” Manager Mạnh Mẽ

React Query (phiên bản mới nhất thuộc về TanStack Query) tự định vị mình là một thư viện quản lý “server state” (trạng thái dữ liệu từ server). Thay vì coi dữ liệu từ API chỉ là một biến state thông thường trong React, React Query nhận ra rằng dữ liệu server có những đặc tính riêng (bất đồng bộ, cần cache, cần revalidate, có thể bị thay đổi bên ngoài ứng dụng)… và cung cấp một bộ công cụ chuyên biệt để xử lý chúng.

Cài đặt React Query

npm install @tanstack/react-query

Hoặc với Yarn:

yarn add @tanstack/react-query

Bạn cần bọc ứng dụng của mình bằng `QueryClientProvider`:

// index.js hoặc App.js
import {
  QueryClient,
  QueryClientProvider,
} from '@tanstack/react-query'

const queryClient = new QueryClient()

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      {/* Các Component con của bạn */}
      <YourApp />
    </QueryClientProvider>
  )
}

export default App

Sử dụng cơ bản với `useQuery`

Hook `useQuery` là trái tim của React Query cho việc fetching dữ liệu. Nó nhận vào một “query key” (một mảng hoặc chuỗi định danh duy nhất cho dữ liệu bạn muốn lấy) và một “query function” (hàm bất đồng bộ để thực hiện request API).

import { useQuery } from '@tanstack/react-query'
import axios from 'axios' // Hoặc fetch

const fetchUsers = async () => {
  const { data } = await axios.get('/api/users')
  return data
}

function UsersList() {
  const { data, error, isLoading, isError } = useQuery({
    queryKey: ['users'], // Query key: ['users']
    queryFn: fetchUsers, // Query function
  })

  if (isLoading) {
    return <div>Đang tải...</div>
  }

  if (isError) {
    return <div>Lỗi: {error.message}</div>
  }

  return (
    <ul>
      {data.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}

React Query sẽ tự động quản lý các trạng thái `isLoading`, `isError`, `data` và `error` cho bạn. Nó cũng tự động cache dữ liệu với query key `[‘users’]`. Nếu component `UsersList` bị unmount và mount lại, hoặc một component khác sử dụng cùng query key `[‘users’]`, React Query sẽ trả về dữ liệu từ cache ngay lập tức (nếu còn hiệu lực) trong khi ngầm thực hiện request mới để đảm bảo dữ liệu là mới nhất (stale-while-revalidate theo mặc định).

Các tính năng nổi bật của React Query

  • Caching mạnh mẽ: Tự động cache dữ liệu dựa trên query key. Cấu hình thời gian cache (cacheTime) và thời gian “stale” (staleTime).
  • Revalidation tự động: Tự động fetch lại dữ liệu khi cửa sổ trình duyệt được focus, khi component mount lại, khi kết nối mạng phục hồi.
  • Background Refetching: Hiển thị dữ liệu cũ từ cache ngay lập tức trong khi ngầm fetch dữ liệu mới, mang lại trải nghiệm người dùng mượt mà.
  • Mutations: Hook `useMutation` giúp xử lý các thao tác tạo/cập nhật/xóa dữ liệu, hỗ trợ optimistic updates và tự động invalidation query cache liên quan.
  • Pagination/Infinite Loading: Hooks và các tiện ích tích hợp sẵn cho việc quản lý dữ liệu theo trang hoặc cuộn vô hạn.
  • Retry Mechanism: Tự động thử lại (retry) các request bị lỗi.
  • Developer Tools: Cung cấp React Query Devtools rất hữu ích để theo dõi cache, query, mutation…

SWR: Stale-While-Revalidate Đơn Giản và Hiệu Quả

SWR là một thư viện fetch dữ liệu khác dành cho React, được phát triển bởi Vercel (công ty đứng sau Next.js). Tên gọi SWR được lấy cảm hứng từ chiến lược cache HTTP `stale-while-revalidate`. Triết lý cốt lõi của SWR là “hiển thị dữ liệu từ cache (stale) ngay lập tức, gửi yêu cầu fetch lại dữ liệu mới (revalidate) ở chế độ nền, và cuối cùng cập nhật UI khi dữ liệu mới về”.

Cài đặt SWR

npm install swr

SWR không yêu cầu bọc toàn bộ ứng dụng trong một Provider mặc định, điều này làm cho việc tích hợp ban đầu có vẻ đơn giản hơn trong một số trường hợp. Bạn có thể sử dụng Provider để cấu hình global, nhưng nó không bắt buộc cho việc sử dụng cơ bản.

import useSWR from 'swr'

// Hàm fetcher đơn giản, thường là wrapper quanh fetch hoặc axios
const fetcher = url => fetch(url).then(res => res.json())
// Hoặc với axios:
// const fetcher = url => axios.get(url).then(res => res.data)

function UsersList() {
  const { data, error, isLoading } = useSWR('/api/users', fetcher)

  if (isLoading) {
    return <div>Đang tải...</div>
  }

  if (error) {
    return <div>Lỗi: {error.message}</div>
  }

  return (
    <ul>
      {data.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}

Hook `useSWR` nhận vào một key (thường là URL endpoint) và một hàm `fetcher`. SWR sẽ tự động thực hiện fetch dữ liệu, quản lý trạng thái `isLoading`, `error`, `data` và áp dụng chiến lược stale-while-revalidate.

Các tính năng nổi bật của SWR

  • Chiến lược Stale-While-Revalidate: Đây là tính năng cốt lõi và được áp dụng mặc định, mang lại trải nghiệm cập nhật dữ liệu rất tốt.
  • Cache tích hợp: Tự động cache dữ liệu theo key.
  • Revalidation tự động: Tự động fetch lại dữ liệu khi cửa sổ trình duyệt được focus, khi kết nối mạng phục hồi, hoặc định kỳ theo cấu hình.
  • Polling: Dễ dàng cấu hình để fetch lại dữ liệu định kỳ theo khoảng thời gian.
  • Mutations: Hook `useSWRMutation` (trong gói `swr/mutation`) hoặc phương thức `mutate` của `useSWR` cho phép thực hiện các thao tác cập nhật dữ liệu và quản lý việc revalidate cache liên quan.
  • Pagination/Infinite Loading: Hook `useSWRInfinite` được cung cấp để xử lý các trường hợp này.
  • Nhẹ nhàng và Tốc độ: Thường có bundle size nhỏ hơn React Query.

React Query vs SWR: So Sánh Chi Tiết

Cả hai thư viện đều giải quyết vấn đề quản lý data fetching rất tốt, nhưng chúng có những điểm khác biệt về triết lý, tính năng và API. Dưới đây là bảng so sánh chi tiết hơn:

Tính năng React Query (TanStack Query) SWR
Triết lý cốt lõi Quản lý “Server State”. Coi dữ liệu server như một loại state đặc biệt cần quản lý cẩn thận. Cung cấp bộ công cụ toàn diện. Dựa trên chiến lược cache HTTP `stale-while-revalidate`. Tập trung vào việc fetch lại dữ liệu một cách thông minh.
API chính `useQuery` cho fetching GET, `useMutation` cho POST/PUT/DELETE. `useSWR` cho fetching GET, `useSWRMutation` (hoặc `mutate` trong `useSWR`) cho POST/PUT/DELETE.
Quản lý Cache Rất mạnh mẽ, cấu hình chi tiết thời gian `staleTime` và `cacheTime`. Cache dữ liệu riêng biệt với state React. Cache tích hợp sẵn, đơn giản hơn. Cache được lưu trữ trong global cache map.
Revalidation Tự động (focus, reconnect, mount). Có thể cấu hình chi tiết các trigger revalidation. Tự động (focus, reconnect, định kỳ). Cốt lõi dựa trên chiến lược SWR.
Mutations Hook `useMutation` riêng biệt, hỗ trợ optimistic updates, `onSuccess`, `onError`, `onSettled`. Dễ dàng invalidation query cache liên quan (`queryClient.invalidateQueries`). Sử dụng `mutate` của `useSWR` hoặc hook `useSWRMutation`. Hỗ trợ optimistic updates. invalidation cache bằng `mutate`.
State Reporting Nhiều trạng thái chi tiết (`isLoading`, `isFetching`, `isSuccess`, `isError`, `status`, `fetchStatus`…). Các trạng thái cơ bản (`isLoading`, `error`, `data`).
Pagination / Infinite Loading Hooks `useInfiniteQuery` và các tùy chọn chi tiết. Hook `useSWRInfinite`.
Developer Tools React Query Devtools rất mạnh mẽ và trực quan. SWR Devtools (cần cài đặt thêm).
Bundle Size (ước tính) Lớn hơn SWR. Nhỏ hơn React Query.
Cộng đồng và Sự phát triển Lớn, rất tích cực, thuộc về TanStack (phát triển nhiều thư viện khác). Lớn, được hỗ trợ bởi Vercel, phát triển tích cực.

Khi nào nên chọn React Query?

  • Ứng dụng phức tạp, nhiều loại dữ liệu: Nếu ứng dụng của bạn có tương tác dữ liệu phức tạp, cần quản lý nhiều loại query khác nhau, cần caching tinh vi, React Query cung cấp bộ công cụ mạnh mẽ và chi tiết hơn.
  • Mutations phức tạp: Nếu bạn thực hiện nhiều thao tác tạo/cập nhật/xóa dữ liệu và cần kiểm soát chặt chẽ optimistic updates, invalidation cache, và các lifecycle hooks của mutation.
  • Cần Devtools mạnh mẽ: React Query Devtools là một điểm cộng lớn, giúp bạn debug và hiểu rõ cách cache và revalidation hoạt động.
  • Quen thuộc với triết lý “Server State”: Nếu bạn thích cách React Query tách biệt và quản lý dữ liệu server như một loại state đặc biệt.
  • Đã sử dụng các thư viện TanStack khác: Nếu bạn đã dùng TanStack Table, TanStack Router…, React Query sẽ phù hợp với hệ sinh thái này.

Khi nào nên chọn SWR?

  • Đề cao sự đơn giản và tốc độ tích hợp: SWR có API `useSWR` đơn giản, không yêu cầu Provider mặc định, giúp bạn bắt đầu nhanh chóng. Bundle size nhỏ hơn cũng là một lợi thế.
  • Ưu tiên chiến lược SWR (Stale-While-Revalidate): Nếu triết lý hiển thị dữ liệu cũ ngay lập tức trong khi ngầm fetch lại dữ liệu mới phù hợp với nhu cầu chính của bạn.
  • Dự án sử dụng Next.js: Vì SWR được phát triển bởi Vercel, nó có sự tích hợp tự nhiên và cộng đồng lớn trong hệ sinh thái Next.js.
  • Cần Polling đơn giản: SWR cung cấp cấu hình polling rất dễ dàng.
  • Dự án quy mô vừa và nhỏ: Với các nhu cầu data fetching cơ bản đến nâng cao vừa phải, SWR là một lựa chọn tuyệt vời với sự đơn giản của nó.

Không chỉ là Fetching: Tích hợp với State Management

Một điểm quan trọng cần hiểu là các thư viện này chủ yếu tập trung vào việc quản lý dữ liệu *từ server*. Chúng không thay thế hoàn toàn các thư viện quản lý global state như Redux Toolkit, Zustand, Recoil, hay Context API (Sử dụng useContext để Quản lý Global State, Chọn Công Cụ Quản Lý State Nào?). Dữ liệu mà các thư viện này quản lý là “server state”, khác với “client state” (ví dụ: trạng thái modal đóng/mở, dữ liệu form chưa submit…).

Tuy nhiên, React Query và SWR làm giảm đáng kể lượng “client state” bạn cần quản lý, vì chúng tự lo các trạng thái loading/error/data của dữ liệu fetch từ server. Điều này giúp đơn giản hóa logic trong các global state store của bạn.

Lời kết

React Query (TanStack Query) và SWR đều là những công cụ tuyệt vời để giải quyết sự phức tạp của việc quản lý dữ liệu bất đồng bộ trong ứng dụng React. Cả hai đều cung cấp các hook mạnh mẽ, quản lý cache, revalidation, và xử lý mutation một cách hiệu quả.

React Query mạnh mẽ hơn ở khả năng cấu hình chi tiết, quản lý “server state” toàn diện và có Devtools rất tốt. Nó phù hợp với các ứng dụng quy mô lớn, phức tạp, đòi hỏi kiểm soát cao đối với các khía cạnh của data fetching.

SWR nổi bật với sự đơn giản, tốc độ tích hợp, và tập trung mạnh mẽ vào chiến lược stale-while-revalidate. Nó là lựa chọn tuyệt vời cho các dự án cần giải pháp nhanh chóng, hiệu quả và tích hợp tốt với Next.js.

Không có câu trả lời đúng tuyệt đối cho việc “nên chọn cái nào”. Lựa chọn phụ thuộc vào nhu cầu cụ thể của dự án, kinh nghiệm của đội ngũ, và triết lý mà bạn ưa thích. Cách tốt nhất là thử nghiệm cả hai trong một dự án nhỏ hoặc POC (Proof of Concept) để xem cái nào phù hợp với bạn hơn.

Hy vọng bài viết này đã cung cấp cho bạn cái nhìn tổng quan và so sánh chi tiết giữa React Query và SWR, giúp bạn đưa ra quyết định sáng suốt cho dự án React tiếp theo của mình. Data fetching là một phần quan trọng trong lộ trình trở thành một React Developer giỏi, và nắm vững các công cụ này sẽ giúp bạn nâng cao hiệu suất và chất lượng code đáng kể.

Tiếp theo trong series React Roadmap, chúng ta sẽ cùng khám phá sâu hơn về một khía cạnh khác không kém phần quan trọng: Testing trong React. Hẹn gặp lại bạn ở bài viết sau!

Các bài viết trong series React Roadmap bạn không nên bỏ lỡ:

Chỉ mục