Zustand Async: 5 Cách Xử Lý Bất Đồng Bộ Hiệu Quả Trong React

Mọi ứng dụng React thực tế đều phải giao tiếp với API bất đồng bộ. Nếu quản lý kém, ứng dụng dễ rơi vào các vấn đề như UI bị đơ, race condition, rò rỉ bộ nhớ, hay hiển thị dữ liệu cũ. Zustand giải quyết bài toán này với một cú pháp cực kỳ đơn giản — định nghĩa async actions trực tiếp trong store, không cần middleware phức tạp như Redux Thunk hay Saga.

Zustandasyncstate managementReact
Ảnh bìa bài viết: Zustand Async: 5 Cách Xử Lý Bất Đồng Bộ Hiệu Quả Trong React
Ảnh đại diện của Trung Vũ Hoàng

Trung Vũ Hoàng

Tác giả

9/5/20263 phút đọc

1. Tại Sao Cần Quản Lý Async State Tập Trung?

Mỗi yêu cầu API cần theo dõi ít nhất 3 trạng thái: loading, success, error. Nếu logic này bị phân tán khắp các component, codebase nhanh chóng trở nên khó đọc và khó debug. Tập trung async state vào Zustand store giúp:

  • Logic fetch tách hoàn toàn khỏi UI component.

  • Nhiều component dùng chung một nguồn dữ liệu duy nhất.

  • Dễ xử lý race condition và hủy request khi component unmount.

2. Cách 1 — Store Async Cơ Bản

Async action trong Zustand chỉ là một hàm async thông thường bên trong store:

import { create } from 'zustand';

const useUserStore = create((set) => ({
  user: null,
  isLoading: false,
  error: null,

  fetchUser: async (userId) => {
    set({ isLoading: true, error: null });
    try {
      const res = await fetch(`/api/users/${userId}`);
      if (!res.ok) throw new Error('Không thể tải dữ liệu!');
      const data = await res.json();
      set({ user: data, isLoading: false });
    } catch (err) {
      set({ error: err.message, isLoading: false });
    }
  },
}));

3. Cách 2 — Dùng Trong Component Với Selector

Chỉ subscribe đúng mảnh state cần thiết để tránh re-render thừa:

const UserProfile = ({ userId }) => {
  const { user, isLoading, error, fetchUser } = useUserStore(state => ({
    user: state.user,
    isLoading: state.isLoading,
    error: state.error,
    fetchUser: state.fetchUser,
  }));

  useEffect(() => {
    fetchUser(userId);
  }, [userId]);

  if (isLoading) return <p>Đang tải...</p>;
  if (error) return <p>Lỗi: {error}</p>;
  return <div>{user?.name}</div>;
};

4. Cách 3 — Song Song Nhiều Request

Dùng Promise.all bên trong action để fetch nhiều nguồn cùng lúc:

fetchDashboard: async () => {
  set({ isLoading: true });
  try {
    const [stats, users, orders] = await Promise.all([
      fetch('/api/stats').then(r => r.json()),
      fetch('/api/users').then(r => r.json()),
      fetch('/api/orders').then(r => r.json()),
    ]);
    set({ stats, users, orders, isLoading: false });
  } catch (err) {
    set({ error: err.message, isLoading: false });
  }
},

5. Cách 4 — Optimistic Update

Cập nhật UI ngay lập tức, rollback nếu API thất bại:

addItem: async (item) => {
  // Cập nhật UI trước
  set(state => ({ items: [...state.items, item] }));
  try {
    await fetch('/api/items', { method: 'POST', body: JSON.stringify(item) });
  } catch (err) {
    // Rollback nếu lỗi
    set(state => ({ items: state.items.filter(i => i.id !== item.id) }));
  }
},

6. Cách 5 — Hủy Request Khi Không Cần Nữa

Dùng AbortController để hủy fetch khi component unmount hoặc userId thay đổi:

fetchUser: async (userId, signal) => {
  set({ isLoading: true });
  try {
    const res = await fetch(`/api/users/${userId}`, { signal });
    const data = await res.json();
    set({ user: data, isLoading: false });
  } catch (err) {
    if (err.name !== 'AbortError') {
      set({ error: err.message, isLoading: false });
    }
  }
},

// Trong component:
useEffect(() => {
  const controller = new AbortController();
  fetchUser(userId, controller.signal);
  return () => controller.abort(); // Hủy khi unmount
}, [userId]);

7. Tối Ưu Với Middleware

  • devtools: Tích hợp Redux DevTools — debug async actions dễ dàng.

  • immer: Cú pháp cập nhật state mutable nhưng vẫn immutable bên dưới.

  • persist: Lưu state vào localStorage, phục hồi khi reload trang.

Tổng Kết

Zustand biến việc xử lý async trong React trở nên gọn nhẹ và tường minh hơn bất kỳ giải pháp nào khác:

  • Cơ bản: async function trực tiếp trong store.

  • Hiệu năng: Selector để tránh re-render thừa.

  • Song song: Promise.all trong action.

  • UX tốt: Optimistic update + rollback.

  • An toàn: AbortController để tránh memory leak.

Câu hỏi thường gặp

Chia sẻ bài viết
Zalo

Bạn thấy bài viết hữu ích?

Liên hệ với chúng tôi để được tư vấn miễn phí về dịch vụ

Liên hệ ngay

Bài viết liên quan

Ảnh bìa bài viết: Promise Nâng Cao: all, allSettled, race, any — Khi Nào Dùng Cái Nào?
Frontend

Promise Nâng Cao: all, allSettled, race, any — Khi Nào Dùng Cái Nào?

Nếu bạn chỉ dùng async/await tuần tự, ứng dụng của bạn đang bỏ phí tiềm năng hiệu suất — mỗi request phải chờ request trước xong mới bắt đầu. Các phương thức tĩnh của Promise (all, allSettled, race, any) cho phép bạn điều phối nhiều tác vụ bất đồng bộ song song theo chiến lược phù hợp từng bài toán.

9/5/2026
Ảnh bìa bài viết: Zustand Async: 5 Cách Xử Lý Bất Đồng Bộ Hiệu Quả Trong React
Frontend

Zustand Async: 5 Cách Xử Lý Bất Đồng Bộ Hiệu Quả Trong React

Mọi ứng dụng React thực tế đều phải giao tiếp với API bất đồng bộ. Nếu quản lý kém, ứng dụng dễ rơi vào các vấn đề như UI bị đơ, race condition, rò rỉ bộ nhớ, hay hiển thị dữ liệu cũ. Zustand giải quyết bài toán này với một cú pháp cực kỳ đơn giản — định nghĩa async actions trực tiếp trong store, không cần middleware phức tạp như Redux Thunk hay Saga.

1/4/2026
Ảnh bìa bài viết: Virtualization Trong React: Kỹ Thuật Xử Lý 100.000 Dòng Dữ Liệu Không Giật Lag
Frontend

Virtualization Trong React: Kỹ Thuật Xử Lý 100.000 Dòng Dữ Liệu Không Giật Lag

Bạn có một danh sách 50.000 dòng cần hiển thị trong React. Nếu render tất cả cùng lúc, trình duyệt sẽ phải dựng hàng chục nghìn DOM node — CPU và RAM rên rỉ, UI giật lag nghiêm trọng, và điểm Core Web Vitals lao dốc. Virtualization (hay Windowing) giải quyết bài toán này bằng một nguyên lý đơn giản mà cực kỳ hiệu quả.

1/4/2026