RTK Query cho mọi thứ là "lấy dữ liệu từ server rồi cache": nó tự lo loading/error, dedupe request trùng, cache theo cache key, providesTags/invalidatesTags để làm mới sau mutation, polling và refetch on focus. Viết tay các thứ này bằng thunk là tái tạo lại một cache layer.
Vẫn dùng createAsyncThunk khi tác vụ bất đồng bộ không phải là cache dữ liệu: một luồng nhiều bước (đăng nhập rồi nạp cấu hình rồi điều hướng), tác vụ cần đọc/ghi nhiều slice qua getState/dispatch, hoặc cần lifecycle pending/fulfilled/rejected phản ánh vào state riêng của bạn.
Chặn request trùng và huỷ:
export const fetchUser = createAsyncThunk(
'user/fetch',
async (id: string, { signal, rejectWithValue }) => {
const res = await fetch(`/api/users/${id}`, { signal })
if (!res.ok) return rejectWithValue(await res.json())
return res.json()
},
{
// bỏ qua nếu đang có request chạy dở
condition: (id, { getState }) => (getState() as RootState).user.status !== 'loading',
},
)condition trả false sẽ không dispatch action nào (thunk bị huỷ trước khi chạy); signal truyền vào fetch cho phép promise.abort() cắt request đang bay.
Dùng rejectWithValue để đưa payload lỗi có cấu trúc vào reducer thay vì chỉ nhận error.message.