- React-Query ๊ธฐ๋ฅ
- ๊ธฐ๋ณธ ์ค์ (QueryClientProvider, QueryClient)
- React Query Devtools
- React Query ์บ์ฑ ๋ผ์ดํ ์ฌ์ดํด
- useQuery
- useQuery ์ฃผ์ ๋ฆฌํด ๋ฐ์ดํฐ
- staleTime๊ณผ cacheTime
- ๋ง์ดํธ ๋ ๋๋ง๋ค ์ฌ์์ฒญํ๋ refetchOnMount
- ์๋์ฐ ํฌ์ปค์ฑ ๋ ๋ ์ฌ ์์ฒญํ๋ refetchOnWindowFocus
- Polling ๋ฐฉ์์ ๊ตฌํํ๊ธฐ ์ํ refetchInterval์ refetchIntervalInBackground)
- ์๋ ์คํ์ enabled์ ์๋์ผ๋ก ์ฟผ๋ฆฌ๋ฅผ ๋ค์ ์์ฒญํ๋ refetch
- ์คํจ ์ฟผ๋ฆฌ์ ๋ํ ์ฌ ์์ฒญํ๋ retry
- onSuccess, onError, onSettled Callback
- select๋ฅผ ์ด์ฉํ ๋ฐ์ดํฐ ๋ณํ
- Paginated ๊ตฌํ์ ์ ์ฉํ keepPreviousData
- ์ฟผ๋ฆฌ๋ฅผ ๋ณ๋ ฌ(Parallel) ์์ฒญํ ์ ์๋ useQueries
- ์ข ์ ์ฟผ๋ฆฌ(Dependent Queries)
- QueryClient ์ธ์คํด์ค๋ฅผ ๋ฐํํ๋ useQueryClient
- ์ด๊ธฐ ๋ฐ์ดํฐ ์ค์ ํ ์ ์๋ initialData
- Infinite Queries
- ์๋ฒ์ Http CUD๊ด๋ จ ์์ ์ ์ํ useMutation๊ณผ mutate
- ์ฟผ๋ฆฌ ๋ฌดํจํ queryClient.invalidateQueries
- ์บ์ ๋ฐ์ดํฐ ์ฆ์ ์ ๋ฐ์ดํธ๋ฅผ ์ํ queryClient.setQueryData
- ์ฌ์ฉ์UX๋ฅผ ์ฌ๋ ค์ฃผ๋ Optimistic Updates(๋๊ด์ ์ ๋ฐ์ดํธ)
- react-query๋ ๋ฆฌ์กํธ ์ ํ๋ฆฌ์ผ์ด์
์์
์๋ฒ ์ํ ๊ฐ์ ธ์ค๊ธฐ
,์บ์ฑ
,๋๊ธฐํ ๋ฐ ์ ๋ฐ์ดํธ
๋ฅผ ๋ณด๋ค ์ฝ๊ฒ ๋ค๋ฃฐ ์ ์๋๋ก ๋์์ฃผ๋ฉฐ ํด๋ผ์ด์ธํธ ์ํ์ ์๋ฒ ์ํ๋ฅผ ๋ช ํํ ๊ตฌ๋ถํ๊ธฐ ์ํด์ ๋ง๋ค์ด์ง ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค. - react-query์์ ๊ธฐ์กด ์ํ ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
(redux, mobX)
๋ํด๋ผ์ด์ธํธ ์ํ ์์
์ ์ ํฉํ์ง๋ง๋น๋๊ธฐ ๋๋ ์๋ฒ ์ํ ์์
์๋ ๊ทธ๋ค์ง ์ข์ง ์๋ค๊ณ ๋งํ๊ณ ์๋ค. - ํด๋ผ์ด์ธํธ ์ํ(Client State) ์ ์๋ฒ ์ํ(Server State)๋ ์์ ํ ๋ค๋ฅด๋ฉฐ ํด๋ผ์ด์ธํธ ์ํ๋ ์ปดํฌ๋ํธ์์ ๊ด๋ฆฌํ๋ ๊ฐ๊ฐ์ input ๊ฐ์ผ๋ก ์๋ฅผ ๋ค ์ ์๊ณ ์๋ฒ ์ํ๋ database์ ์ ์ฅ๋์ด์๋ ๋ฐ์ดํฐ๋ก ์๋ฅผ ๋ค ์ ์๋ค.
-
์๋
- React Query์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ์์น์ ๋ฐ์ดํฐ๊ฐ ์ผ๋ง๋ ํ์ํ์ง ์๋ ค์ฃผ๋ฉด ๋๋จธ์ง๋ ์๋์ด๋ค. React Query๋ ๊ตฌ์ฑ์ด ํ์ ์๋ ์ฆ์
์บ์ฑ
,๋ฐฑ๊ทธ๋ผ์ด๋ ์ ๋ฐ์ดํธ
๋ฐ ์ค๋๋ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค.
- React Query์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ์์น์ ๋ฐ์ดํฐ๊ฐ ์ผ๋ง๋ ํ์ํ์ง ์๋ ค์ฃผ๋ฉด ๋๋จธ์ง๋ ์๋์ด๋ค. React Query๋ ๊ตฌ์ฑ์ด ํ์ ์๋ ์ฆ์
-
์น์ํ๊ณ ๊ฐ๋จํ๋ค.
promise
๋๋async/await
๋ก ์์ ํ๋ ๋ฐฉ๋ฒ์ ์๊ณ ์๋ค๋ฉด ์ด๋ฏธ React Query๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์๊ณ ์๋ ๊ฒ์ด๋ค. ๊ด๋ฆฌํ ์ ์ญ ์ํ, ๊ฐ์๊ธฐ, ์ ๊ทํ ์์คํ ๋๋ ์ดํดํด์ผ ํ ๋ฌด๊ฑฐ์ด ๊ตฌ์ฑ์ด ์๋ค. ๋ฐ์ดํฐ๋ฅผ ํด๊ฒฐํ๋(๋๋ ์ค๋ฅ๋ฅผ ๋ฐ์์ํค๋) ํจ์๋ฅผ ์ ๋ฌํ๊ธฐ๋ง ํ๋ฉด๋๋ค.
-
๊ฐ๋ ฅํ ๋๊ตฌ ๋ฐ ๊ตฌ์ฑ
- React Query๋ ๋ชจ๋ ์ฌ์ฉ ์ฌ๋ก์ ๋ง๋ ๋
ธ๋ธ์ ์ต์
์ ์ฌ์ฉํ์ฌ ์ฟผ๋ฆฌ์ ๊ฐ ๊ด์ฐฐ์ ์ธ์คํด์ค๊น์ง ๊ตฌ์ฑํ ์ ์๋ค.
์ ์ฉ devtools
,๋ฌดํ ๋ก๋ฉ API
๋ฐ๋ฐ์ดํฐ ์ ๋ฐ์ดํธ
๋ฅผ ์ฝ๊ฒ ๋ง๋ค์ด์ฃผ๋ mutation ๋๊ตฌ๊ฐ ์๋ค.
- React Query๋ ๋ชจ๋ ์ฌ์ฉ ์ฌ๋ก์ ๋ง๋ ๋
ธ๋ธ์ ์ต์
์ ์ฌ์ฉํ์ฌ ์ฟผ๋ฆฌ์ ๊ฐ ๊ด์ฐฐ์ ์ธ์คํด์ค๊น์ง ๊ตฌ์ฑํ ์ ์๋ค.
-
์ ์ ์ฝ๋. ๋ ์ ์ ์ฃ์ง ์ผ์ด์ค.
๋ฆฌ๋์
,์บ์ฑ ๋ก์ง
,ํ์ด๋จธ
,์ฌ์ฌ์ฉ ๋ก์ง
,๋ณต์กํ ๋น๋๊ธฐ/๋๊ธฐ ์คํฌ๋ฆฝํ
์ ํ์ ํ๋ ์ฝ๋๋ณด๋ค ์ ์ ์์ ์ฝ๋๋ก ์์ฑํ ์ ์๋ค.
// QueryClient ์์
import { QueryClient } from "react-query";
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: Infinity,
},
},
});
- QueryClient๋ฅผ ์ฌ์ฉํ์ฌ
์บ์
์ ์ํธ ์์ฉํ ์ ์์ต๋๋ค. - QueryClient์์
๋ชจ๋ query
๋๋mutation
์ ๊ธฐ๋ณธ ์ต์ ๋ค์ ์ถ๊ฐํ ์ ์๋๋ฐ ์ข ๋ฅ๊ฐ ์๋นํ๋ ์๋จ์ ๊ณต์ ์ฌ์ดํธ๋ฅผ ์ฐธ๊ณ ํ์.
// QueryClientProvider + QueryClient
import { QueryClient, QueryClientProvider } from "react-query";
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
<div>๋ธ๋ผ๋ธ๋ผ</div>
</QueryClientProvider>;
);
}
- App ์ ์ฒด์์ ๋ฆฌ์กํธ ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋
QueryClientProvider
๋ฅผ ์ต์๋จ์์ ๊ฐ์ธ์ฃผ๊ณQueryClient
๋ฅผ Props๋ก ๋ฃ์ด์ค์ผ ํ๋ค. - App.js์
QueryClientProvider
๋ก ์ดํ ์ปดํฌ๋ํธ๋ฅผ ๊ฐ์ธ๊ณqueryClient
๋ฅผ ๋ด๋ ค๋ณด๋ด์ค โ ์ด context๋ ์ฑ์์ ๋น๋๊ธฐ ์์ฒญ์ ์์์ ์ฒ๋ฆฌํ๋ background ๊ณ์ธต์ด๋๋ค. QueryClientProvider
๋ ๊ตฌ์ฑ ์์๋ฅผ ์ฌ์ฉํ์ฌ QueryClient๋ฅผ ์ฐ๊ฒฐํ๊ณ ์์ฉ ํ๋ก๊ทธ๋จ์ ์ ๊ณตํ๋ค.
- React Query๋
์ ์ฉ devtools
๋ฅผ ์ ๊ณตํ๋ค. - devtools๋ฅผ ์ฌ์ฉํ๋ฉด React Query์ ๋ชจ๋ ๋ด๋ถ ๋์์ ์๊ฐํํ๋๋ฐ ๋์์ด ๋๋ฉฐ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ฉด ๋๋ฒ๊น ์๊ฐ์ ์ ์ฝํ ์ ์๋ค.
import { ReactQueryDevtools } from "react-query/devtools";
<AppContext.Provider value={user}>
<QueryClientProvider client={queryClient}>
// ...
<ReactQueryDevtools initialIsOpen={false} position="bottom-right" />
</QueryClientProvider>
</AppContext.Provider>;
- initialIsOpen (Boolean):
true
์ด๋ฉด ๊ฐ๋ฐ ๋๊ตฌ๊ฐ ๊ธฐ๋ณธ์ ์ผ๋ก ์ด๋ ค ์๋๋ก ์ค์ ํ ์ ์๋ค. - panelProps (PropsObject): ํจ๋์
props
์ ์ถ๊ฐํ ์ ์๋ค. ์๋ฅผ ๋ค์ด className, style, onClick ๋ฑ - closeButtonProps( PropsObject): ๋ซ๊ธฐ ๋ฒํผ์ props๋ฅผ ์ถ๊ฐํ ์ ์๋ค.
- toggleButtonProps (PropsObject): ํ ๊ธ ๋ฒํผ์ props๋ฅผ ์ถ๊ฐํ ์ ์๋ค.
- position?: ("top-left" | "top-right" | "bottom-left" | "bottom-right")
- ๊ธฐ๋ณธ๊ฐ:
bottom-left
- devtools ํจ๋์ ์ด๊ณ ๋ซ๊ธฐ ์ํ ๋ก๊ณ ์์น
- ๊ธฐ๋ณธ๊ฐ:
- React-Query ์บ์ ๋ผ์ดํ ์ฌ์ดํด
* Query Instances with and without cache data(์บ์ ๋ฐ์ดํฐ๊ฐ ์๊ฑฐ๋ ์๋ ์ฟผ๋ฆฌ ์ธ์คํด์ค)
* Background Refetching(๋ฐฑ๊ทธ๋ผ์ด๋ ๋ฆฌํจ์นญ)
* Inactive Queries(๋นํ์ฑ ์ฟผ๋ฆฌ)
* Garbage Collection(๊ฐ๋น์ง ์ปฌ๋ ์
)
- A ์ฟผ๋ฆฌ ์ธ์คํด์ค๊ฐ
mount
๋จ - ๋คํธ์ํฌ์์ ๋ฐ์ดํฐ
fetch
ํ๊ณ A๋ผ๋query key
๋ก ์บ์ฑํจ - ์ด ๋ฐ์ดํฐ๋
fresh
์ํ์์staleTime(๊ธฐ๋ณธ๊ฐ 0)
์ดํstale
์ํ๋ก ๋ณ๊ฒฝ๋จ - A ์ฟผ๋ฆฌ ์ธ์คํด์ค๊ฐ
unmount
๋จ - ์บ์๋
cacheTime(๊ธฐ๋ณธ๊ฐ 5min)
๋งํผ ์ ์ง๋๋ค๊ฐ๊ฐ๋น์ง ์ฝ๋ ํฐ
๋ก ์์ง๋จ - ๋ง์ผ cacheTime์ด ์ง๋๊ธฐ ์ ์ด๊ณ , ๋ฐ์ดํฐ๊ฐ stale ์ํ์ผ ๊ฒฝ์ฐ A ์ฟผ๋ฆฌ ์ธ์คํด์ค๊ฐ ์๋กญ๊ฒ mount๋๋ฉด, fetch๊ฐ ์คํ๋๊ณ freshํ ๊ฐ์ ๊ฐ์ ธ์ค๋ ๋์ ์บ์ ๋ฐ์ดํฐ๋ฅผ ๋ณด์ฌ์ค
// ์ฌ์ฉ๋ฒ(1)
const { data, isLoading, ... } = useQuery(queryKey, queryFn, {
//์ต์
๋ค ex) enabled, staleTime
});
// ์ฌ์ฉ๋ฒ(2)
const result = useQuery({
queryKey,
queryFn,
//์ต์
๋ค ex) enabled, staleTime
});
// ์ค์ ์์
const getSuperHero = useCallback(() => {
return axios.get("http://localhost:4000/superheroes");
}, []);
const { isLoading, data } = useQuery("super-heroes", getSuperHero);
- useQuery๋ ๊ธฐ๋ณธ์ ์ผ๋ก 3๊ฐ์ ์ธ์๋ฅผ ๋ฐ์ต๋๋ค. ์ฒซ ๋ฒ์งธ ์ธ์๊ฐ
queryKey(ํ์)
, ๋ ๋ฒ์งธ ์ธ์๊ฐqueryFn(ํ์)
, ์ธ ๋ฒ์งธ ์ธ์๊ฐoptions
์ ๋๋ค. - useQuery๋ ์ฒซ ๋ฒ์งธ ์ธ์์ธ
queryKey
๋ฅผ ๊ธฐ๋ฐ์ผ๋ก๋ฐ์ดํฐ ์บ์ฑ
์ ๊ด๋ฆฌํฉ๋๋ค.๋ฌธ์์ด
๋๋๋ฐฐ์ด
๋ก ์ง์ ํ ์ ์๋๋ฐ, ์ผ๋ฐ์ ์ผ๋ก๋ ์ ์์ ์ฒ๋ผ๋ฌธ์์ด
๋ก ์ง์ ํ ์ ์์ง๋ง, ๋ง์ฝ ์ฟผ๋ฆฌ๊ฐ ํน์ ๋ณ์์ ์์กดํ๋ ๊ฒฝ์ฐ์๋ ์๋ ์์ ์ฒ๋ผ๋ฐฐ์ด
๋ก ์ง์ ํด ํด๋น ๋ณ์๋ฅผ ์ถ๊ฐํด์ฃผ์ด์ผ ํฉ๋๋ค. - ์ฌ์ฉ๋ฒ
(1)
๋ฒ๊ณผ(2)
๋ฒ ๋๋ค ์ฌ์ฉ๋๋๋ฐ. ์ ๊ทผ ๋ฐฉ์์ ์ฐจ์ด์ ๋๋ค. ๋ ๊ฐ์ง ๋ฐฉ์ ๋ชจ๋ ์ ์ดํดํ๊ณ ์ฌ์ฉํฉ์๋ค.
// (1)
const fetchSuperHero = ({ queryKey }: any) => {
const heroId = queryKey[1]; // queryKey: (2) ['super-hero', '3']
return axios.get(`http://localhost:4000/superheroes/${heroId}`);
};
const useSuperHeroData = (heroId: string) => {
return useQuery(["super-hero", heroId], fetchSuperHero);
};
// (2)
const fetchSuperHero = (heroId: string) => {
return axios.get(`http://localhost:4000/superheroes/${heroId}`);
};
const useSuperHeroData = (heroId: string) => {
return useQuery(["super-hero", heroId], () => fetchSuperHero(heroId));
};
- useQuery์ ๋ ๋ฒ์งธ ์ธ์์ธ queryFn๋
promise
๋ฅผ ๋ฐํํ๋ ํจ์๋ฅผ ๋ฃ์ด์ฃผ์ด์ผ ํฉ๋๋ค. - useQuery์ ์ธ ๋ฒ์งธ ์ธ์์ธ
options
์ ๋ง์ด ์ฐ์ด๋ ์ต์ ๋ค์ ๋ฐ์์ ์ค๋ช ํ์์ต๋๋ค. ๊ทธ์ธ์๋ ์์ useQuery ์ฐธ๊ณ ์ฌ์ดํธ๋ฅผ ํตํด ํ์ธํ๋ฉด๋๋ค.
- ์ฐธ๊ณ ๋ก ๋์ค์ queryClient๋ก ํน์ key์ ํด๋นํ๋ query์ ์ ๊ทผํ ๋๋ ์ด๊ธฐ์ ์ค์ ํด๋ ํฌ๋งท์ ์ง์ผ์ค์ผ ์ ๋๋ก ๊ฐ์ ธ์ฌ ์ ์๋ค.
- ์๋ ์์ ๋ฅผ ์ฐธ๊ณ ํ๋ฉด useQuery์์ queryKey์ ํด๋นํ๋ ํฌ๋งท์ด ๋ฐฐ์ด
["super-hero", heroId]
์ด๋ค. ๊ทธ๋ ๋ค๋ฉด ๋ฐ์ useMutation์์ setQueryData๋ฅผ ์ด์ฉํ ๋ ๋๊ฐ์ด["super-hero", heroId]
ํฌ๋งท์ ๊ฐ์ ธ์ผํ๋ค.
// ์
const useSuperHeroData = (heroId: string) => {
return useQuery(["super-hero", heroId], () => fetchSuperHero(heroId));
};
const useAddSuperHeroData = () => {
const queryClient = useQueryClient();
return useMutation(addSuperHero, {
onSuccess(data) {
// ์ ๋๋ก ๋ชป๊ฐ์ ธ์ด! ํฌ๋งท์๋ง์! ["super-hero", heroId] ๋กํด์ผ๋
queryClient.setQueryData("super-hero", (oldData: any) => {
return {
...oldData,
data: [...oldData.data, data.data],
};
});
},
onError(err) {
console.log(err);
},
});
};
- useQuery ๊ณต์ ์ฌ์ดํธ ์ฐธ๊ณ
- status: ์ฟผ๋ฆฌ ์์ฒญ ํจ์์ ์ํ๋ฅผ ํํํ๋ status๋ 4๊ฐ์ง์ ๊ฐ์ด ์กด์ฌํ๋ค.(๋ฌธ์์ด ํํ)
- idle: ์ฟผ๋ฆฌ ๋ฐ์ดํฐ๊ฐ ์๊ณ ๋น์์ ๋, { enabled: false } ์ํ๋ก ์ฟผ๋ฆฌ๊ฐ ํธ์ถ๋๋ฉด ์ด ์ํ๋ก ์์๋๋ค.
- loading: ๋ง ๊ทธ๋๋ก ๋ก๋ฉ์ค์ผ ๋ ์ํ
- error: ์๋ฌ ๋ฐ์ํ์ ๋ ์ํ
- success: ์์ฒญ ์ฑ๊ณตํ์ ๋ ์ํ
- data: ์ฟผ๋ฆฌ ํจ์๊ฐ ๋ฆฌํดํ Promise์์
resolved
๋ ๋ฐ์ดํฐ - isLoading:
์บ์ฑ๋ ๋ฐ์ดํฐ๊ฐ ์์๋!
fetch ๊ณผ์ ์ค์ true ์ฆ, ์บ์ฑ ๋ฐ์ดํฐ๊ฐ ์์ผ๋ฉด false - isFetching: ๋ฐ์ดํฐ๊ฐ
fetch
๋ ๋ false์์ true๊ฐ ๋๋ค. ์บ์ฑ ๋ฐ์ดํฐ๊ฐ ์์ด์ ๋ฐฑ๊ทธ๋ผ์ด๋์์ fetch ๋๋๋ผ๋ true์ด๋ค. - error: ์ฟผ๋ฆฌ ํจ์๊ฐ ์ค๋ฅ๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ์ ๋ํ ์ค๋ฅ ๊ฐ์ฒด
- isError: ์๋ฌ๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ true
- ๊ทธ์ธ ๋ฆฌํด ๋ฐ์ดํฐ๋ค์ ํ์ธํ๊ณ ์ถ์ผ๋ฉด ์๋จ์ ๊ณต์ ์ฌ์ดํธ ์ฐธ๊ณ
const { isLoading, isError, error, data, isFetching } = useQuery(
["colors", pageNum],
() => fetchColors(pageNum)
);
- useQuery ๊ณต์ ์ฌ์ดํธ ์ฐธ๊ณ
- ์๋ ์์ ๋ค ์ ์ธํ๊ณ ์ถ๊ฐ์ ์ธ ์ต์ ๋ค์ ์ ์ฌ์ดํธ ์ฐธ๊ณ
- stale์ ์ฉ์ด ๋ป๋๋ก
์ฉ์
์ด๋ผ๋ ์๋ฏธ์ด๋ค. ์ฆ, ์ต์ ์ํ๊ฐ ์๋๋ผ๋ ์๋ฏธ์ด๋ค. - fresh๋ ๋ป ๊ทธ๋๋ก
์ ์ ํ
์ด๋ผ๋ ์๋ฏธ์ด๋ค. ์ฆ, ์ต์ ์ํ๋ผ๋ ์๋ฏธ์ด๋ค.
const { isLoading, isFetching, data, isError, error } = useQuery(
"super-heroes",
getSuperHero,
{
cacheTime: 3000,
staleTime: 50000,
}
);
- staleTime
- staleTime์ ๋ฐ์ดํฐ๊ฐ
fresh์์ stale
์ํ๋ก ๋ณ๊ฒฝ๋๋๋ฐ ๊ฑธ๋ฆฌ๋ ์๊ฐ, ๋ง์ฝ staleTime์ด 3000์ด๋ฉด fresh์ํ์์ 3์ด๋ค์ stale๋ก ๋ณํ fresh
์ํ์ผ๋๋ ์ฟผ๋ฆฌ ์ธ์คํด์ค๊ฐ ์๋กญ๊ฒ mount ๋์ด๋ ๋คํธ์ํฌ ์์ฒญ(fetch)์ด ์ผ์ด๋์ง ์๋๋ค.- ๋ฐ์ดํฐ๊ฐ ํ๋ฒ fetch ๋๊ณ ๋์
staleTime
์ด ์ง๋์ง ์์๋ค๋ฉด(fresh์ํ) unmount ํ mount ๋์ด๋ fetch๊ฐ ์ผ์ด๋์ง ์๋๋ค. - staleTime์ ๊ธฐ๋ณธ๊ฐ์
0
์ด๊ธฐ ๋๋ฌธ์ ์ผ๋ฐ์ ์ผ๋ก fetch ํ์ ๋ฐ๋ก stale์ด ๋๋ค.
- staleTime์ ๋ฐ์ดํฐ๊ฐ
- cacheTime
- ๋ฐ์ดํฐ๊ฐ
inactive
์ํ์ผ ๋์บ์ฑ๋ ์ํ๋ก
๋จ์์๋ ์๊ฐ - ์ฟผ๋ฆฌ ์ธ์คํด์ค๊ฐ unmount ๋๋ฉด ๋ฐ์ดํฐ๋
inactive ์ํ๋ก ๋ณ๊ฒฝ
๋๋ฉฐ, ์บ์๋cacheTime
๋งํผ ์ ์ง๋๋ค. - cacheTime์ด ์ง๋๋ฉด
๊ฐ๋น์ง ์ฝ๋ ํฐ
๋ก ์์ง๋๋ค. - cacheTime์ด ์ง๋๊ธฐ ์ ์ ์ฟผ๋ฆฌ ์ธ์คํด์ค๊ฐ ๋ค์ mount ๋๋ฉด, ๋ฐ์ดํฐ๋ฅผ fetchํ๋ ๋์ ์บ์ ๋ฐ์ดํฐ๋ฅผ ๋ณด์ฌ์ค๋ค.
- cacheTime์ staleTime๊ณผ ๊ด๊ณ์์ด, ๋ฌด์กฐ๊ฑด inactive ๋ ์์ ์ ๊ธฐ์ค์ผ๋ก ์บ์ ๋ฐ์ดํฐ ์ญ์ ๋ฅผ ๊ฒฐ์ ํ๋ค.
- cacheTime์ ๊ธฐ๋ณธ๊ฐ์
5๋ถ
- ๋ฐ์ดํฐ๊ฐ
const { isLoading, isFetching, data, isError, error } = useQuery(
"super-heroes",
getSuperHero,
{
refetchOnMount: true,
}
);
- refetchOnMount๋ ๋ฐ์ดํฐ๊ฐ
stale
์ํ์ผ ๊ฒฝ์ฐ ๋ง์ดํธ ์ ๋ง๋คrefetch
๋ฅผ ์คํํ๋ ์ต์ ์ด๋ค. - ๊ธฐ๋ณธ๊ฐ์
true
์ธ๋ฐ ์ด๊ฒ ๋ฒ ์คํธ๋ค. 'always'
๋ก ์ค์ ํ๋ฉด ๋ง์ดํธ ์ ๋ง๋ค ๋งค๋ฒ refetch๋ฅผ ์คํํ๋ค.false
๋ก ์ค์ ํ๋ฉด ์ต์ด fetch ์ดํ์๋ refetchํ์ง ์๋๋ค.
const { isLoading, isFetching, data, isError, error } = useQuery(
"super-heroes",
getSuperHero,
{
refetchOnWindowFocus: true,
}
);
- refetchOnWindowFocus๋ ๋ฐ์ดํฐ๊ฐ
stale
์ํ์ผ ๊ฒฝ์ฐ์๋์ฐ ํฌ์ปค์ฑ
๋ ๋ ๋ง๋ค refetch๋ฅผ ์คํํ๋ ์ต์ ์ด๋ค. - ์๋ฅผ ๋ค์ด, ํฌ๋กฌ์์ ๋ค๋ฅธ ํญ์ ๋๋ ๋ค๊ฐ ๋ค์ ์๋ ๋ณด๋ ์ค์ธ ํญ์ ๋๋ ์ ๋๋ ์ด ๊ฒฝ์ฐ์ ํด๋นํ๋ค. ์ฌ์ง์ด F12๋ก ๊ฐ๋ฐ์ ๋๊ตฌ ์ฐฝ์ ์ผ์ ๋คํธ์ํฌ ํญ์ด๋ , ์ฝ์ ํญ์ด๋ ๊ฐ๋ฐ์ ๋๊ตฌ ์ฐฝ์์ ๋๋ค๊ฐ ํ์ด์ง ๋ด๋ถ๋ฅผ ๋ค์ ํด๋ฆญํ์ ๋๋ ์ด ๊ฒฝ์ฐ์ ํด๋นํ๋ค.
- ๊ธฐ๋ณธ๊ฐ์
true
์ด๋ค. always
๋ก ์ค์ ํ๋ฉด ํญ์ ์๋์ฐ ํฌ์ปค์ฑ ๋ ๋ ๋ง๋ค refetch๋ฅผ ์คํํ๋ค๋ ์๋ฏธ์ด๋ค.
const { isLoading, isFetching, data, isError, error } = useQuery(
"super-heroes",
getSuperHero,
{
// refetchInterval: 2000,
refetchIntervalInBackground: true,
}
);
- ํด๋ง์ด๋? ๋ฆฌ์ผํ์ ์น์ ์ํ ๊ธฐ๋ฒ์ผ๋ก
์ผ์ ํ ์ฃผ๊ธฐ(ํน์ ํ ์๊ฐ)
๋ฅผ ๊ฐ์ง๊ณ ์๋ฒ์ ์๋ต์ ์ฃผ๊ณ ๋ฐ๋ ๋ฐฉ์์ด ํด๋ง ๋ฐฉ์์ด๋ค. - react-query์์๋
refetchInterval
์ ์ด์ฉํด์ ๊ตฌํํ ์ ์๋ค. refetchIntervalInBackground
์ผ๋ก๋ ํด๋ง์ ๊ตฌํํ ์ ์๋๋ฐrefetchInterval
ํญ/์ฐฝ์ด ๋ฐฑ๊ทธ๋ผ์ด๋์ ์๋ ๋์ ๊ณ์ ๋ค์ ๊ฐ์ ธ์ต๋๋ค.
const { isLoading, isFetching, data, isError, error, refetch } = useQuery(
"super-heroes",
getSuperHero,
{
enabled: false,
}
);
const handleClickRefetch = useCallback(() => {
refetch();
}, [refetch]);
return (
<div>
{data?.data.map((hero: Data) => (
<div key={hero.id}>{hero.name}</div>
))}
<button onClick={handleClickRefetch}>Fetch Heroes</button>
</div>
);
enabled
๋ ์ฟผ๋ฆฌ๊ฐ ์๋์ผ๋ก ์คํ๋์ง ์๋๋ก ํ ๋ ์ค์ ํ ์ ์๋ค.false
๋ฅผ ์ฃผ๋ฉด ์๋ ์คํ๋์ง ์๋๋ค. ๋ํ useQuery ๋ฆฌํด ๋ฐ์ดํฐ์ค status๊ฐ idle ์ํ๋ก ์์ํ๋ค.refetch
๋ ์ฟผ๋ฆฌ๋ฅผ์๋
์ผ๋ก ๋ค์ ์์ฒญํ๋ ๊ธฐ๋ฅ์ด๋ค. ์ฟผ๋ฆฌ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ฉด ์ค๋ฅ๋ง ๊ธฐ๋ก๋๋ค. ์ค๋ฅ๋ฅผ ๋ฐ์์ํค๋ ค๋ฉดthrowOnError
์์ฑ์true
๋กํด์ ์ ๋ฌํด์ผ ํ๋ค.- ๋ณดํต ์๋์ผ๋ก ์ฟผ๋ฆฌ ์์ฒญ์ ํ์ง ์๊ณ ๋ฒํผ ํด๋ฆญ์ด๋ ํน์ ์ด๋ฒคํธ๋ฅผ ํตํด ์์ฒญ์ ์๋ํ ๋ ๊ฐ์ด ์ฌ์ฉํ๋ค.
const result = useQuery(["todos", 1], fetchTodoListPage, {
retry: 10, // ์ค๋ฅ๋ฅผ ํ์ํ๊ธฐ ์ ์ ์คํจํ ์์ฒญ์ 10๋ฒ ์ฌ์๋ํฉ๋๋ค.
});
- retry๋ ์ฟผ๋ฆฌ๊ฐ ์คํจํ๋ฉด useQuery๋ฅผ ํน์ ํ์(๊ธฐ๋ณธ๊ฐ3)๋งํผ ์ฌ์์ฒญํ๋ ์ต์ ์ด๋ค.
- retry๊ฐ
false
์ธ ๊ฒฝ์ฐ ์คํจํ ์ฟผ๋ฆฌ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ค์ ์๋ํ์ง ์๋๋ค. true
์ธ ๊ฒฝ์ฐ์๋ ์คํจํ ์ฟผ๋ฆฌ์ ๋ํด์ ๋ฌดํ ์ฌ์์ฒญ์ ์๋ํ๋ค.- ๊ฐ์ผ๋ก
์ซ์
๋ฅผ ๋ฃ์ ๊ฒฝ์ฐ ์คํจํ ์ฟผ๋ฆฌ๊ฐ ํด๋น ์ซ์๋ฅผ ์ถฉ์กฑํ ๋๊น์ง ์์ฒญ์ ์ฌ์๋ํ๋ค.
const onSuccess = useCallback((data) => {
console.log("Success", data);
}, []);
const onError = useCallback((err) => {
console.log("Error", err);
}, []);
const onSettled = useCallback(() => {
console.log("Settled");
}, []);
const { isLoading, isFetching, data, isError, error, refetch } = useQuery(
"super-heroes",
getSuperHero,
{
onSuccess,
onError,
onSettled,
}
);
onSuccess
ํจ์๋ ์ฟผ๋ฆฌ ์์ฒญ์ด ์ฑ๊ณต์ ์ผ๋ก ์งํ๋์ ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ฑฐ๋ ์บ์๊ฐ ์ ๋ฐ์ดํธ๋ ๋๋ง๋ค ์คํ๋๋ค.onError
ํจ์๋ ์ฟผ๋ฆฌ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ณ ์ค๋ฅ๊ฐ ์ ๋ฌ๋๋ฉด ์คํ๋๋ค.onSettled
ํจ์๋ ์ฟผ๋ฆฌ ์์ฒญ์ด ์ฑ๊ณต, ์คํจ ๋ชจ๋ ์คํ๋๋ค.
const { isLoading, isFetching, data, isError, error, refetch } = useQuery(
"super-heroes",
getSuperHero,
{
onSuccess,
onError,
select(data) {
const superHeroNames = data.data.map((hero: Data) => hero.name);
return superHeroNames;
},
}
);
return (
<div>
<button onClick={handleClickRefetch}>Fetch Heroes</button>
{data.map((heroName: string, idx: number) => (
<div key={idx}>{heroName}</div>
))}
</div>
);
select
์ต์ ์ ์ฌ์ฉํ์ฌ ์ฟผ๋ฆฌ ํจ์์์ ๋ฐํ๋ ๋ฐ์ดํฐ์ ์ผ๋ถ๋ฅผ ๋ณํํ๊ฑฐ๋ ์ ํํ ์ ์๋ค.
const fetchColors = (pageNum: number) => {
return axios.get(`http://localhost:4000/colors?_limit=2&_page=${pageNum}`);
};
const { isLoading, isError, error, data, isFetching, isPreviousData } =
useQuery(["colors", pageNum], () => fetchColors(pageNum), {
keepPreviousData: true,
});
keepPreviousData
๋ฅผ true๋ก ์ค์ ํ๋ฉด ์ฟผ๋ฆฌ ํค๊ฐ ๋ณ๊ฒฝ๋์ด์ ์๋ก์ด ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ๋ ๋์์๋ ๋ง์ง๋ง data ๊ฐ์ ์ ์งํ๋ค.keepPreviousData
์ํ์ด์ง๋ค์ด์
๊ณผ ๊ฐ์ ๊ธฐ๋ฅ์ ๊ตฌํํ ๋ ํธ๋ฆฌํ๋ค. ์บ์๋์ง ์์ ํ์ด์ง๋ฅผ ๊ฐ์ ธ์ฌ ๋ ๋ชฉ๋ก์ด๊น๋นก๊น๋นก๊ฑฐ๋ฆฌ๋ ํ์์ ๋ฐฉ์ง
ํ ์ ์๋ค.- ๋ํ,
isPreviousData
๊ฐ์ผ๋ก ํ์ฌ์ ์ฟผ๋ฆฌ ํค์ ํด๋นํ๋ ๊ฐ์ธ์ง ํ์ธํ ์ ์๋ค.
function Todos() {
const placeholderData = useMemo(() => generateFakeTodos(), []);
const result = useQuery("todos", () => fetch("/todos"), { placeholderData });
}
- placeholderData๋ฅผ ์ฌ์ฉํ๋ฉด
mock ๋ฐ์ดํฐ
์ค์ ๋ ๊ฐ๋ฅํ๋ค. ๋์ ์บ์ฑ์ด ์๋๋ค๋ ๋จ์ ์ด์๋ค.
const { data: superHeroes } = useQuery("super-heroes", fetchSuperHeroes);
const { data: friends } = useQuery("friends", fetchFriends);
- ๋ช ๊ฐ์ง ์ํฉ์ ์ ์ธํ๋ฉด ์ฟผ๋ฆฌ ์ฌ๋ฌ๊ฐ๊ฐ ์ ์ธ๋์ด ์๋ ์ผ๋ฐ์ ์ธ ์ํฉ์ผ ๋ ์ฟผ๋ฆฌ ํจ์๋ค์
๊ทธ๋ฅ ๋ณ๋ ฌ๋ก ์์ฒญ๋์ ์ฒ๋ฆฌ
๋๋ค. - ์ด๋ฌํ ํน์ง์ ์ฟผ๋ฆฌ ์ฒ๋ฆฌ์
๋์์ฑ
์ ๊ทน๋ํ ์ํจ๋ค.
const queryResults = useQueries(
heroIds.map((id) => ({
queryKey: ["super-hero", id],
queryFn: () => fetchSuperHero(id),
}))
);
- ํ์ง๋ง, ์ฟผ๋ฆฌ ์ฌ๋ฌ๊ฐ๋ฅผ ๋์์ ์ํํด์ผ ํ๋๋ฐ, ๋ ๋๋ง์ด ๊ฑฐ๋ญ๋๋ ์ฌ์ด์ฌ์ด์ ๊ณ์ ์ฟผ๋ฆฌ๊ฐ ์ํ๋์ด์ผ ํ๋ค๋ฉด ์ฟผ๋ฆฌ๋ฅผ ์ํํ๋ ๋ก์ง์ด hook๋ฃฐ์ ์๋ฐฐ๋ ์๋ ์๋ค. ์ด๋ด ๋๋
uesQueries
๋ฅผ ์ฌ์ฉํ๋ค.
์ข ์ ์ฟผ๋ฆฌ
๋ ์ด๋ค A๋ผ๋ ์ฟผ๋ฆฌ๊ฐ ์๋๋ฐ ์ด A์ฟผ๋ฆฌ๋ฅผ ์คํํ๊ธฐ ์ ์ ์ฌ์ ์ ์๋ฃ๋์ผ ํ๋ B ์ฟผ๋ฆฌ๊ฐ ์๋๋ฐ, ์ด๋ฌํ B์ฟผ๋ฆฌ์ ์์กดํ๋ A์ฟผ๋ฆฌ๋ฅผ ์ข ์ ์ฟผ๋ฆฌ๋ผ๊ณ ํ๋ค.- react-query์์๋ ์ฟผ๋ฆฌ๋ฅผ ์คํํ ์ค๋น๊ฐ ๋์๋ค๋ ๊ฒ์ ์๋ ค์ฃผ๋
enabled
์ต์ ์ ํตํด ์ข ์ ์ฟผ๋ฆฌ๋ฅผ ์ฝ๊ฒ ๊ตฌํํ ์ ์๋ค.
const DependantQueriesPage = ({ email }: Props) => {
// ์ฌ์ ์ ์๋ฃ๋์ผํ ์ฟผ๋ฆฌ
const { data: user } = useQuery(['user', email], () =>
fetchUserByEmail(email)
);
const channelId = user?.data.channelId;
// user ์ฟผ๋ฆฌ์ ์ข
์ ์ฟผ๋ฆฌ
const { data } = useQuery(
['courses', channelId],
() => fetchCoursesByChannelId(channelId),
{ enabled: !!channelId }
);
- useQueryClient๋
QueryClient
์ธ์คํด์ค๋ฅผ ๋ฐํํ๋ค. QueryClient
๋ ์บ์์ ์ํธ์์ฉ ํ๋ค.
import { useQueryClient } from "react-query";
const queryClient = useQueryClient();
- ์ฟผ๋ฆฌ์ ๋ํ
์ด๊ธฐ ๋ฐ์ดํฐ
๊ฐ ํ์ํ๊ธฐ ์ ์ ์บ์์ ์ ๊ณตํ๋ ๋ฐฉ๋ฒ์ด ์๋ค. ์๋ ์์ ์ฐธ๊ณ - initialData ์ต์ ์ ํตํด์ ์ฟผ๋ฆฌ๋ฅผ ๋ฏธ๋ฆฌ ์ฑ์ฐ๋๋ฐ ์ฌ์ฉํ ์ ์์ผ๋ฉฐ, ์ด๊ธฐ ๋ก๋ ์ํ๋ ๊ฑด๋๋ ์ ์๋ค.
const useSuperHeroData = (heroId: string) => {
const queryClient = useQueryClient();
return useQuery(['super-hero', heroId], fetchSuperHero, {
initialData: () => {
const queryData = queryClient.getQueryData('super-heroes') as any;
const hero = queryData?.data?.find(
(hero: Hero) => hero.id === parseInt(heroId)
);
if (hero) return { data: hero };
return undefined;
},
});
};
- ์ฐธ๊ณ ๋ก ์ ์์ ์์
queryClient.getQueryData
๋ฉ์๋๋ ๊ธฐ์กด ์ฟผ๋ฆฌ์์บ์๋ ๋ฐ์ดํฐ
๋ฅผ ๊ฐ์ ธ์ค๋๋ฐ ์ฌ์ฉํ ์ ์๋ ๋๊ธฐ ํจ์์ด๋ค. ์ฟผ๋ฆฌ๊ฐ ์กด์ฌํ์ง ์์ผ๋ฉดundefined
๋ฅผ ๋ฐํํ๋ค.
- ๋ฌดํ ์ฟผ๋ฆฌ๋ ๋ฌดํ ์คํฌ๋กค์ด๋ load more ๊ฐ์ด ํน์ ์กฐ๊ฑด์์ ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐ์ ์ผ๋ก ๋ฐ์์ค๋ ๊ธฐ๋ฅ ๊ตฌํํ ๋ ์ฌ์ฉํ๋ฉด ์ ์ฉํ๋ค.
import { useInfiniteQuery } from "react-query";
const fetchColors = ({ pageParam = 1 }) => {
return axios.get(`http://localhost:4000/colors?_limit=2&_page=${pageParam}`);
};
const InfiniteQueries = () => {
const { data, hasNextPage, isFetching, isFetchingNextPage, fetchNextPage } =
useInfiniteQuery("colors", fetchColors, {
getNextPageParam: (lastPage, allPages) => {
if (allPages.length < 4) {
return allPages.length + 1;
} else {
return undefined;
}
},
});
return (
<div>
{data?.pages.map((group, idx) => (
<Fragment key={idx}>
{group.data.map((color: any) => (
<h2 key={color.id}>
{color.id}. {color.label}
</h2>
))}
</Fragment>
))}
<div>
<button disabled={!hasNextPage} onClick={() => fetchNextPage()}>
LoadMore
</button>
</div>
<div>{isFetching && !isFetchingNextPage ? "Fetching..." : null}</div>
</div>
);
};
Returns
useInfiniteQuery
๋ ๊ธฐ๋ณธ์ ์ผ๋ก useQuery์ ์ฌ์ฉ๋ฒ์ ๋น์ทํ์ง๋ง ์ฐจ์ด์ ์ด ์๋ค.- useInfiniteQuery๋ ๋ฐํ๊ฐ์ผ๋ก isFetchingNextPage, isFetchingPreviousPage, fetchNextPage, fetchPreviousPage ๋ฑ์ด ์ถ๊ฐ์ ์ผ๋ก ์๋ค.
fetchNextPage
๋ฅผ ํธ์ถํ๋ฉด ๋ค์ ํ์ด์ง๋ฅผ fetch ํ ์ ์๋ค.fetchPreviousPage
๋ฅผ ํธ์ถํ๋ฉด ์ด์ ํ์ด์ง๋ฅผ fetch ํ ์ ์๋ค.isFetchingNextPage
์fetchNextPage
๋ฉ์๋๊ฐ ๋ค์ ํ์ด์ง๋ฅผ ๊ฐ์ ธ์ค๋ ๋์ true์ด๋ค. ์ฆ, ์ด๊ธฐ๊ฐ์ true์ด๊ณ , ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ฉด false๊ฐ ๋๋ค.isFetchingPreviousPage
์fetchPreviousPage
๋ฉ์๋๊ฐ ์ด์ ํ์ด์ง๋ฅผ ๊ฐ์ ธ์ค๋ ๋์ true์ด๋ค. ์ฆ, ์ด๊ธฐ๊ฐ์ true์ด๊ณ , ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ฉด false๊ฐ ๋๋ค.hasNextPage
๋ ๊ฐ์ ธ์ฌ ์ ์๋ ๋ค์ ํ์ด์ง๊ฐ ์์ ๊ฒฝ์ฐ true์ด๋ค.
์ต์
pageParam
์ด๋ผ๋ ํ๋กํผํฐ๊ฐ ์กด์ฌํ๋ฉฐ queryFn์ ํ ๋นํด์ค์ผํ๋ค. ์ด๋ ๊ธฐ๋ณธ๊ฐ์1
๋ก ํด์ค์ผํ๋ค.- ๊ทธ๋ฆฌ๊ณ
getNextPageParam
์ ์ด์ฉํด์ ํ์ด์ง๋ฅผ ์ฆ๊ฐ์ํจ๋ค.- ์ด๋, getNextPageParam์ ์ฒซ ๋ฒ์งธ ์ธ์
lastPage
๋ fetch ํด์จ ๊ฐ์ฅ ์ต๊ทผ์ ๊ฐ์ ธ์จ ํ์ด์ง ๋ชฉ๋ก์ด๋ค. - ๋ ๋ฒ์งธ ์ธ์
allPages
๋ ํ์ฌ๊น์ง ๊ฐ์ ธ์จ ๋ชจ๋ ํ์ด์ง๋ค ๋ฐ์ดํฐ์ด๋ค.
- ์ด๋, getNextPageParam์ ์ฒซ ๋ฒ์งธ ์ธ์
getPreviousPageParam
๋ ์กด์ฌํ๋ฉฐ,getNextPageParam
์ ๋ฐ๋์ ์์ฑ์ ๊ฐ๊ณ ์๋ค.- ๊ทธ๋ฆฌ๊ณ ์์ฒญ์ด ์ฑ๊ณตํ๊ณ ๋ฐํ๋๋ data๋
pages
๋ผ๋ ํ๋กํผํฐ๋ฅผ ๊ฐ๊ณ ์์ผ๋ฉฐ, pages๋group
์ด๋ผ๋ ํ๋กํผํฐ๋ฅผ ๊ฐ๊ณ ์๋ค.
- useMutation ๊ณต์ ์ฌ์ดํธ
- react-query์์ ๊ธฐ๋ณธ์ ์ผ๋ก ์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ Get ํ ๋๋ useQuery๋ฅผ ์ฌ์ฉํ๋ค.
- ๋ง์ฝ ์๋ฒ์ data๋ฅผ post, patch, put, delete์ ๊ฐ์ด ์์ ํ๊ณ ์ ํ๋ค๋ฉด ์ด๋๋ useMutation์ ์ด์ฉํ๋ค.
- ์์ฝํ์๋ฉด
R(read)๋ useQuery
,CUD(Create, Update, Delete)๋ useMutation
์ ์ฌ์ฉํ๋ค.
const CreateTodo = () => {
const mutation = useMutation(createTodo, {
onMutate() {},
onSuccess(data) {
console.log(data);
},
onError(err) {
console.log(err);
},
});
const onCreateTodo = (e) => {
e.preventDefault();
mutation.mutate({ title });
};
return <>...</>;
};
- useMutation์ ๋ฐํ ๊ฐ์ธ mutation ๊ฐ์ฒด์
mutate
๋ฉ์๋๋ฅผ ์ด์ฉํด์ ์์ฒญ ํจ์๋ฅผ ํธ์ถํ ์ ์๋ค. - mutate๋
onSuccess
,onError
๋ฉ์๋๋ฅผ ํตํด ์ฑ๊ณต ํ์ ์, ์คํจ ํ์ ์ response ๋ฐ์ดํฐ๋ฅผ ํธ๋ค๋งํ ์ ์๋ค. onMutate
๋ mutation ํจ์๊ฐ ์คํ๋๊ธฐ ์ ์ ์คํ๋๊ณ mutation ํจ์๊ฐ ๋ฐ์ ๋์ผํ ๋ณ์๊ฐ ์ ๋ฌ๋๋ค.
const mutation = useMutation(addTodo);
try {
const todo = await mutation.mutateAsync(todo);
console.log(todo);
} catch (error) {
console.error(error);
} finally {
console.log("done");
}
- ๋ง์ฝ, useMutation์ ์ฌ์ฉํ ๋ promise ํํ์ response๊ฐ ํ์ํ ๊ฒฝ์ฐ๋ผ๋ฉด
mutateAsync
๋ฅผ ์ฌ์ฉํด์ ์ป์ด์ฌ ์ ์๋ค. - Redux์ ๊ฐ์ Request ์ฑ๊ณต ์ก์
์ ๋ฏธ๋ค์จ์ด์์ ํ์ธํ์ฌ ์ถ๊ฐ ์ก์
์ ์คํํ๋ ๊ฒ ๊ฐ์ ์์
์ ํ ๋๋, mutate๋ onSuccess, onError์ ๊ฐ์ ๋ฉ์๋๋ฅผ ๊ฐ์ด ์ฌ์ฉํด์ผ ๋๊ธฐ๋๋ฌธ์
mutateAsync๊ฐ ๋ ๊ฐ๋ ์ฑ์ด ์ข๋ค
- ์ด๊ฒ์ ๊ฐ๋ ์ ์ผ๋ก ํ๋ฉด์ ์ต์ ์ํ๋ก ์ ์งํ๋ ๊ฐ์ฅ ๊ฐ๋จํ ๋ฐฉ๋ฒ์ด๋ค..
- ์๋ฅผ ๋ค๋ฉด, ๊ฒ์ํ ๋ชฉ๋ก์์ ์ด๋ค ๊ฒ์๊ธ์
์์ฑ(Post)
ํ๊ฑฐ๋ ๊ฒ์๊ธ์์ ๊ฑฐ(Delete)
ํ์ ๋ ํ๋ฉด์ ๋ณด์ฌ์ฃผ๋ ๊ฒ์ํ ๋ชฉ๋ก์ ์ค์๊ฐ ์ต์ ํ ํด์ผ๋ ๋๊ฐ ์๋ค. - ํ์ง๋ง ์ด๋,
query Key
๊ฐ ๋ณํ์ง ์์ผ๋ฏ๋ก ์ด๋ด๋ ๊ฐ์ ๋ฆฌํ๋ ์ฌ๋ฅผ ์งํํด์ผ ํ๋๋ฐ ์ด๋,queryClient
์invalidateQueries()
๋ฉ์๋๋ฅผ ์ด์ฉํ๋ค. - ์ฆ, query๊ฐ ์ค๋ ๋์๋ค๋ ๊ฒ์ ํ๋จํ๊ณ ๋ค์
refetch
๋ฅผ ํ ๋ ์ฌ์ฉํ๋ค!
import { useMutation, useQuery, useQueryClient } from "react-query";
const useAddSuperHeroData = () => {
const queryClient = useQueryClient();
return useMutation(addSuperHero, {
onSuccess(data) {
queryClient.invalidateQueries("super-heroes"); // ์ด key์ ํด๋น ํ๋ ์ฟผ๋ฆฌ๊ฐ ๋ฌดํจํ!
console.log(data);
},
onError(err) {
console.log(err);
},
});
};
- ๋ง์ฝ ๋ฌดํจํ ํ๋ ค๋ ํค๊ฐ ์ฌ๋ฌ๊ฐ๋ผ๋ฉด ์๋ ์์ ์ ๊ฐ์ด ๋ค์๊ณผ ๊ฐ์ด ๋ฐฐ์ด๋ก ๋ณด๋ด์ฃผ๋ฉด ๋๋ค.
queryClient.invalidateQueries(["super-heroes", "posts", "comment"]);
- ๋ฐ๋ก ์์์
queryClient.invalidateQueries
๋ฅผ ์ด์ฉํด ์บ์ ๋ฐ์ดํฐ๋ฅผ ์ต์ ํํ๋ ๋ฐฉ๋ฒ์ ์์๋ดค๋๋ฐ queryClient.setQueryData๋ฅผ ์ด์ฉํด์๋ ๋ฐ์ดํฐ๋ฅผ ์ฆ์ ์ ๋ฐ์ดํธ ํ ์ ์๋ค. queryClient.setQueryData
๋ ์ฟผ๋ฆฌ์ ์บ์๋ ๋ฐ์ดํฐ๋ฅผ ์ฆ์ ์ ๋ฐ์ดํธํ๋ ๋ฐ ์ฌ์ฉํ ์ ์๋๋๊ธฐ ํจ์
์ด๋ค.
const useAddSuperHeroData = () => {
const queryClient = useQueryClient();
return useMutation(addSuperHero, {
onSuccess(data) {
queryClient.setQueryData("super-heroes", (oldData: any) => {
return {
...oldData,
data: [...oldData.data, data.data],
};
});
},
onError(err) {
console.log(err);
},
});
};
Optimistic Update(๋๊ด์ ์ ๋ฐ์ดํธ)
๋ ์๋ฒ ์ ๋ฐ์ดํธ ์ UI์์๋ ์ด์ฐจํผ ์ ๋ฐ์ดํธ ํ ๊ฒ์ด๋ผ๊ณ (๋๊ด์ ์ธ) ๊ฐ์ ํด์๋ฏธ๋ฆฌ UI๋ฅผ ์ ๋ฐ์ดํธ
์์ผ์ฃผ๊ณ ์๋ฒ๋ฅผ ํตํด ๊ฒ์ฆ์ ๋ฐ๊ณ ์ ๋ฐ์ดํธ ๋๋ ๋กค๋ฐฑํ๋ ๋ฐฉ์์ด๋ค.- ์๋ฅผ ๋ค์ด facebook์ ์ข์์ ๋ฒํผ์ด ์๋๋ฐ ์ด๊ฒ์ ์ ์ ๊ฐ ๋๋ฅธ๋ค๋ฉด ์ผ๋จ client ์ชฝ state๋ฅผ ๋จผ์ ์ ๋ฐ์ดํธํ๋ค. ๊ทธ๋ฆฌ๊ณ ๋ง์ฝ์ ์คํจ ํ๋ค๋ฉด ์์ state๋ก ๋์๊ฐ๊ณ ์ฑ๊ณตํ๋ฉด ํ์ํ ๋ฐ์ดํฐ๋ฅผ ๋ค์ fetchํด์ ์๋ฒ ๋ฐ์ดํฐ์ ํ์คํ ์ฐ๋์ ์งํํ๋ค.
- Optimistic Update๊ฐ ์ ๋ง ์ ์ฉํ ๋๋ ์ธํฐ๋ท ์๋๊ฐ ๋๋ฆฌ๊ฑฐ๋ ์๋ฒ๊ฐ ๋๋ฆด ๋ ์ ์ ๊ฐ ํ ์ก์ ์ ๊ธฐ๋ค๋ฆด ํ์ ์์ด ๋ฐ๋ก ์ ๋ฐ์ดํธ ๋๋ ๊ฒ์ฒ๋ผ ๋ณด์ด๊ธฐ ๋๋ฌธ์ UX์ ์ผ๋ก ์ข๋ค.
const useAddSuperHeroData = () => {
const queryClient = useQueryClient();
return useMutation(addSuperHero, {
async onMutate(newHero) {
// ๋๊ด์ ์
๋ฐ์ดํธ๋ฅผ ๋ฎ์ด์ฐ์ง ์๊ธฐ ์ํด Cancelํ๋ค.
await queryClient.cancelQueries("super-heroes");
// ์ด์ ๊ฐ
const previousHeroData = queryClient.getQueryData("super-heroes");
// ์๋ก์ด ๊ฐ์ผ๋ก ๋๊ด์ ์
๋ฐ์ดํธ ์งํ
queryClient.setQueryData("super-heroes", (oldData: any) => {
return {
...oldData,
data: [
...oldData.data,
{ ...newHero, id: oldData?.data?.length + 1 },
],
};
});
// ๊ฐ์ด ๋ค์ด์๋ context ๊ฐ์ฒด๋ฅผ ๋ฐํ
return {
previousHeroData,
};
},
// mutation์ด ์คํจํ๋ฉด onMutate์์ ๋ฐํ๋ context๋ฅผ ์ฌ์ฉํ์ฌ ๋กค๋ฐฑ ์งํ
onError(error, hero, context: any) {
queryClient.setQueryData("super-heroes", context.previousHeroData);
},
// ์ค๋ฅ ๋๋ ์ฑ๊ณต ํ์๋ ํญ์ ๋ฆฌํ๋ ์ฌ
onSettled() {
queryClient.invalidateQueries("super-heroes");
},
});
};
- ์ฐธ๊ณ ๋ก ์ ์์ ์์ cancelQueries๋ ์ฟผ๋ฆฌ๋ฅผ ์ทจ์์ํฌ ์ ์๋ค. ์ทจ์์ํฌ query์ key๋ฅผ cancelQueries์ ์ธ์๋ก ๋ณด๋ด ์คํ์ํจ๋ค.
- cancelQueries๊ฐ ์คํ๋๋ฉด ์ฒ์ query์์ ๋ฐํํ promise ๊ฐ์ฒด์ cancel์ ์๋์ผ๋ก ํธ์ถํ๋ค๊ณ ํ๋ค.