Hướng dẫn xử lý data fetching với RTK Query trong Redux Toolkit
1. Tổng quan Overview
Nếu bạn đã và đang xử dụng redux để quản lý state với dự án của mình. Việc
xử lý data fetching hay handle các async action là một việc thường xuyên
phải thực hiện. Nắm được nhu cầu đó, hàng loạt các package được ra đời để
giải quyết các bài toán từ cơ bản đến phức tạp: redux-toolkit, redux-thunk,
redux-saga, swr, react-query,...
Với xu hướng khi mà cấu trúc từ dạng cổ điển của redux
(actions-types-reducers) đang dần bị thay thế bởi kiến
trúc Slice mà
redux-toolkit
cung cấp tối ưu hơn, tinh giản và gọn nhẹ hơn.
Nó trở một thế lực ngày càng trở nên mạnh mẽ và dần trở thành cặp bài trùng
với redux.
Bởi lý do đó, cùng với bài toán làm sao để tối ưu source code trở nên nhanh
hơn, nhẹ hơn nhưng vẫn tiện lợi, redux-toolkit đã tung ra một tính năng rất
quan trọng
RTK Query Cùng xem qua những trường hợp nào bạn sẽ cần nó nhé (phần này mình
trích nguồn từ
trang chủ của redux-toolkit):
Data Fetching
- Sử dụng RTK Query như một cách tiếp cận mặc định cho fetching và caching
- Nếu như RTKQ không đủ đáp ứng vì một vài lý do nào đó, hãy sử dụng createAsyncThunk
- Chỉ quay về handle thủ công với thunks nếu không có biện pháp nào hiệu quả
- Đừng sử dụng sagas hoặc observables cho data fetching
- Reacting to Actions / State Changes, Async Workflows
Xử dụng RTK đển lắng nghe những phản hồi cập nhật từ store và xử lý những task vụ bất đồng bộ trong thời gian dài
- Chỉ xử dụng sagas/observables nếu listeners không xử lý được các trường hợp của bạn tốt.
- Logic với State Acess
Login with State Access
- Sử dụng thunks để xử lý các logic xác thực không đồng bộ, bao gồm cả getState và dispatching multiple actions
2. So sánh với các package khác
Đầu tiên, giống như việc redux core được lấy cảm hứng từ các công cụ như
Flux và Elm, RTK Query cũng được xây dựng dựa trên Api design patterns các
các concepts phổ biến giống như các thư viện khác: react query, swr,... Tất
cả các công cụ trên đều rất tuyệt vời, nếu bạn đang sử dụng một trong những
thư viện trên và đang hài lòng với nó, hãy tiếp tục sử dụng chúng không nhất
thiết phải ép buộc chuyển sang RTK Query.
RTK Query có một số khía cạnh và Api design patterns độc đáo đáng để xem xét:
- Với React Query và SWR, bạn thường tự khởi tạo các hook của mình và bạn có thể làm điều đó mọi lúc mọi nơi. Với RTK Query, bạn thực hiện việc này ở một vị trí trung tâm bằng cách xác định trước "API Slice" với nhiều điểm cuối. Điều này cho phép model được tích hợp chặt chẽ hơn, tự động invalidating/refetching các query khi kích hoạt.
- Vì RTK Query gửi các Redux action giống như 1 query được processed, nên tất cả action đều hiển thị trong Redux DevTools. Ngoài ra, mọi request đều tự động hiển thị với Redux reducers của bạn và có thể dễ dàng cập nhật global state nếu cần (ví dụ). Bạn có thể sử dụng Matchers để thực hiện xử lý bổ sung các hành động liên quan đến bộ đệm trong bộ giảm tốc của riêng bạn.
- Giống như Redux, chức năng chính của RTK Query không phụ thuộc vào UI-agnostic và có thể được sử dụng với bất kỳ UI layer nào
- Bạn có thể dễ dàng vô hiệu hóa các entities hoặc patch đang tồn tại trong query data (bằng hàm util.updateQueryData) từ middleware.
- RTK Query cho phép streaming cache updates, chẳng hạn như cập nhật dữ liệu được tìm nạp ban đầu khi nhận được messages qua websocket và cũng được tích hợp tính năng hỗ trợ cho optimistic updates.
- RTK Query cung cấp một fetch wrapper rất nhỏ và linh hoạt: FetchBaseQuery. Cũng rất dễ dàng để tích hợp vs custom với ứng dụng của bạn, giống như sử dụng axios, redaxios hoặc thứ gì đó tùy chỉnh.
Tất nhiên tính năng nào hay thì sẽ cần có cái phải đánh đổi, đó là quy luật.
Không có bộ đệm được chuẩn hóa hoặc loại bỏ dulicated
RTK Query cố tình không triển khai bộ đệm ẩn có thể loại bỏ các
mục giống hệt nhau trên nhiều request. Có một số lý do cho việc này (cái này
mình trích từ trang chủ của redux-toolkit, nhìn chung là author hơi xạo tí
^^):
- Bộ nhớ đệm shared-across-queries là một vấn đề khó giải quyết
- Nhà phát triển không có thời gian, nguồn lực hoặc hứng thú để giải quyết vấn đề đó ngay bây giờ
- Trong nhiều trường hợp, chỉ cần refetching khi dữ liệu invalid sẽ hoạt động tốt và dễ hiểu hơn
- Với mức tối thiểu, RTKQ có thể giúp giải quyết trường hợp sử dụng chung là "fetch some data", đây là điểm khó khăn lớn đối với nhiều người
Bundle Size
RTK Query thêm
fixed one-time amount vào bundle-size của bạn. Vì RTKQ sẽ có depend với Redux và
React-Redux nên kích thước được thêm vào sẽ khác nhau tùy thuộc vào việc bạn
có đang sử dụng những thứ đó trong ứng dụng của mình hay không. Kích thước
gói tối thiểu + gzip ước tính là:
- Nếu bạn sử dụng RTK already: ~9kb cho RTKQ và ~2kb cho các hook.
- Nếu bạn chưa sử dụng RTK already:
- Không có React: 17 kB cho RTK+dependencies+RTK Query
- Với React: 19kB + React-Redux - peer dependency
Việc thêm các định nghĩa additional endpoint chỉ nên tăng kích thước dựa
trên mã thực tế bên trong các endpoints definitions, thường chỉ có vài
byte.
Chức năng có trong RTKQ nhanh chóng đáp ứng kích thước gói được thêm vào
và việc loại bỏ logic tìm nạp dữ liệu viết tay sẽ là một cải tiến thực sự
về kích thước cho hầu hết các ứng dụng.
Bảng so sánh tổng quát
| Feature | rtk-query | react-query | apollo | urql |
|---|---|---|---|---|
| Supported Protocols | any, REST included | any, none included | GraphQL | GraphQL |
| API Definition | declarative | on use, declarative | GraphQL schema | GraphQL schema |
| Cache by | endpoint + serialized arguments | user-defined query-key | type/id | type/id? |
| Invalidation Strategy + Refetching | declarative, by type and/or type/id | manual by cache key | automatic cache updates on per-entity level, manual query invalidation by cache key | declarative, by type OR automatic cache updates on per-entity level, manual query invalidation by cache key |
| Polling | yes | yes | yes | yes |
| Parallel queries | yes | yes | yes | yes |
| Dependent queries | yes | yes | yes | yes |
| Skip queries | yes | yes | yes | yes |
| Lagged queries | yes | yes | no | ? |
| Auto garbage collection | yes | yes | no | ? |
| Normalized caching | no | no | yes | yes |
| Infinite scrolling | TODO | yes | requires manual code | ? |
| Prefetching | yes | yes | yes | yes? |
| Retrying | yes | yes | requires manual code | ? |
| Optimistic updates | can update cache by hand | can update cache by hand | optimisticResponse | ? |
| Manual cache manipulation | yes | yes | yes | yes |
| Platforms | hooks for React, everywhere Redux works | hooks for React | various | various |
3. Thực hành với một ví dụ đơn giản
Create Endpoint
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; const usersApi = createApi({ baseQuery: fetchBaseQuery({ baseUrl: "/users" }), endpoints: (builder) => ({ fetchUser: builder.query({ query: (id) => `user/${id}`, }), createUser: builder.mutation({ query: (user) => ({ url: "user", method: "POST", body: user, }), }), }), }); export const { useFetchUserQuery, useCreateUserMutation } = usersApi;
Thêm service vào store
import { configureStore } from '@reduxjs/toolkit'
// Or from '@reduxjs/toolkit/query/react'
import { setupListeners } from '@reduxjs/toolkit/query'
import { usersApi } from './services/usersApi'
export const store = configureStore({
reducer: {
// Add the generated reducer as a specific top-level slice
[usersApi.reducerPath]: usersApi.reducer,
},
// Adding the api middleware enables caching, invalidation, polling,
// and other useful features of `rtk-query`.
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(usersApi.middleware),
})
// optional, but required for refetchOnFocus/refetchOnReconnect behaviors
// see `setupListeners` docs - takes an optional callback as the 2nd arg for customization
setupListeners(store.dispatch)
Bọc application với Provider
import * as React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import App from './App'
import { store } from './app/store'
const rootElement = document.getElementById('root')
render(
<Provider store={store}>
<App />
</Provider>,
rootElement
)
Sử dụng với hook
import React from "react";
import { useFetchUserQuery, useCreateUserMutation } from "./usersApi";
const UserProfile = ({ userId }) => {
const { data, error, isLoading } = useFetchUserQuery(userId);
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return ( <div> <h1>{data.name}</h1> <p>Email: {data.email}</p> </div> ); };
const CreateUserForm = () => {
const [createUser, { isLoading }] = useCreateUserMutation();
const handleSubmit = async (user) => { try { await createUser(user).unwrap(); alert("User created successfully!"); } catch (error) { alert(`Error: ${error.message}`); } }; // Render form components and handle form submission with handleSubmit };
Cách call request trực tiếp mà không cần hook
import { usersApi } from "./usersApi";
const fetchUserById = async (id) => { try { const result = await usersApi.endpoints.fetchUser.initiate(id).unwrap(); console.log("User data:", result); } catch (error) { console.error("Error fetching user:", error); } };
fetchUserById(1);
Cách cập nhật manual cache
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post'],
endpoints: (build) => ({
getPost: build.query({
query: (id) => `post/${id}`,
providesTags: ['Post'],
}),
updatePost: build.mutation({
query: ({ id, ...patch }) => ({
url: `post/${id}`,
method: 'PATCH',
body: patch,
}),
async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) {
const patchResult = dispatch(
api.util.updateQueryData('getPost', id, (draft) => {
Object.assign(draft, patch)
})
)
try { await queryFulfilled } catch { patchResult.undo() /**
* Alternatively, on failure you can invalidate the corresponding cache tags
* to trigger a re-fetch:
* dispatch(api.util.invalidateTags(['Post']))
*/ } }, }), }), })
Polling
import * as React from 'react'
import { useGetPokemonByNameQuery } from './services/pokemon'
export const Pokemon = ({ name }: { name: string }) => {
// Automatically refetch every 3s
const { data, status, error, refetch } = useGetPokemonByNameQuery(name, {
pollingInterval: 3000,
})
return <div>{data}</div>
}
4. Kết luận
Redux Toolkit cung cấp một cách tiếp cận mạnh mẽ và có khả năng thích ứng để nâng cao khả năng tìm nạp dữ liệu, bộ đệm và quản lý trạng thái của ứng dụng React của bạn. Bằng cách nắm vững kiểm soát endpoint, tận dụng các hooks được tạo tự động và sử dụng lệnh gọi trực tiếp mà không cần ghim, bạn có thể mở khóa toàn bộ khả năng của RTK Query và xây dựng các ứng dụng có thể mở rộng và bảo trì dễ dàng hơn.
Hãy ngưng dùng redux thunk hay redux saga, RTK Query của redux-toolkit là tất cả những gì bạn cần
Reviewed by David
on
tháng 10 06, 2024
Rating:
Reviewed by David
on
tháng 10 06, 2024
Rating:

Không có nhận xét nào: