주어진 시간에 대해 시스템을 나타내는 것으로 언제든지 변경할 수 있음
문자열, 배열, 객체 등의 형태로 응용프로그램에 저장된 데이터
상태들은 시간에 따라 변화함
React 에서는 단방향 바인딩이므로 Props Drilling 이슈도 존재
Redux, MobX, Rematch 와 같은 라이브러리를 활용해 해결함.
상태관리 영역이 서버 값을 저장하는데까지 확장
API 통신 관련 코드가 모두 store에?
또, 반복되는 isFetching, isError 등 API 관련 상태
또또, 반복되는 비슷한 구조의 API 통신 코드
KeyPoint. 데이터 OwnerShip이 있는곳!
| Client State | Server State |
|---|---|
| Client에서 소유하며 온전히 제어가능함 | Client에서 제어하거나 소유되지 않은 원격의 공간에서 관리되고 유지됨 |
| 초기값 설정이나 조작에 제약사항 없음 | Fetching/Updatind에 비동기 API가 필요함 |
| 다른 사람들과 공유되지 않으며 Client 내에서 UI/UX 흐름이나 사용자 인터렉션에 따라 변할 수 있음 | 다른 사람들과 공유되는 것으로 사용자가 모르는 사이에 변경될 수 있음 |
| 항상 Client 내에서 최신 상태로 관리됨 | 신경 쓰지 않는다면 잠재적으로 "out of date"가 될 가능성을 지님 |
Onwership이 Client에 | Onwership이 Server에 |
💡 fetching, caching, 서버 데이터와의 동기화를 지원해주는 라이브러리
서버 상태 가져오기, 캐싱, 동기화 및 업데이트를 보다 쉽게 다룰 수 있도록 도와주는 라이브러리이다. 클라이언트 상태와 서버 상태를 명확히 구분하기 위해 만들어졌다.redux, mobX가 클라이언트 상태 작업에 적합하지만, 비동기 또는 서버 상태 작업에는 그다지 좋지 않다고 언급한다.// v4
import { QueryClient } from "@tanstack/react-query";
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: Infinity,
// ...
},
},
});
캐시와 상호 작용할 수 있다.query 또는 mutation에 기본 옵션을 추가할 수 있으며, 종류가 상당하기 때문에 공식 사이트를 참고해보자.import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient({ /* options */});
function App() {
return (
<QueryClientProvider client={queryClient}>
<div>Text</div>
</QueryClientProvider>;
);
}
QueryClientProvider를 최상단에서 감싸주고 QueryClient 인스턴스를 client props로 넣어 애플리케이션에 연결시켜야 한다.background 계층이 된다.※ Devtools URL: react-query devtools
// v3
import { ReactQueryDevtools } from "react-query/devtools";
<AppContext.Provider value={user}>
<QueryClientProvider client={queryClient}>
// ...
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</AppContext.Provider>;
v4부터는 devtools를 위한 별도의 패키지 설치가 필요하다.
$ npm i @tanstack/react-query-devtools
# or
$ pnpm add @tanstack/react-query-devtools
# or
$ yarn add @tanstack/react-query-devtools
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
function App() {
return (
<QueryClientProvider client={queryClient}>
{/* The rest of your application */}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
* Query Instances with and without cache data(캐시 데이터가 있거나 없는 쿼리 인스턴스)
* Background Refetching(백그라운드 리패칭)
* Inactive Queries(비활성 쿼리)
* Garbage Collection(가비지 컬렉션)
순서
// 사용법
const { data, isLoading, ... } = useQuery(queryKey, queryFn, {
...options
});
or
// 사용법
const result = useQuery({
queryKey,
queryFn,
...options
});
// 실제 사용법
const KeywordFetch = async () => {
return await httpClient.get<resKeywordDto>(
SearchControllerPath.keywordGet
);
};
const useKeyword = () => {
const { data } = useQuery(['keyword'], keywordFetch, {
staleTime: 1000 * 60 * 6,
cacheTime: 1000 * 60 * 10
});
const keyword = useMemo(() => data?.data, [data]);
return { keyword };
};
queryKey(필수), 두 번째 인자가 queryFn(필수), 세 번째 인자가 options(optional)이다.queryKey
v3까지는 queryKey로 문자열 또는 배열 모두 지정할 수 있는데, v4부터는 무조건 배열로 지정해야 한다.queryKey를 기반으로 데이터 캐싱을 관리한다.queryFn
options
옵션들을 알고싶다면 useQuery 공식 문서를 통해 확인해보자.
const { status, isLoading, isError, error, data, isFetching, ... } = useQuery(
["search", keyword],
() => keywordFetch(keyword)
);
staleTime과 cacheTime
const { data } = useQuery(['keyword'], keywordFetch, {
staleTime: 1000 * 60 * 6, // 6분
cacheTime: 1000 * 60 * 10 // 10분
});
refetchOnMount
const { data } = useQuery(['popularKeyword'], PopularKeywordFetch, {
refetchOnMount: 'always',
});
추가적인 옵션은 useQuery v4 공식 문서 참고
import { useInfiniteQuery } from "@tanstack/react-query";
const KeywordFetch = async () => {
return await httpClient.get<resKeywordDto>(
SearchControllerPath.keywordGet
);
};
const InfiniteQueries = () => {
const { data, hasNextPage, isFetching, isFetchingNextPage, fetchNextPage } =
useInfiniteQuery(["keyword"], KeywordFetch, {
getNextPageParam: (lastPage, allPages) => {
return allPages.length < 4 && allPages.length + 1;
},
});
return (
<div>
{data?.pages.map((group, idx) => ({
/* ... */
}))}
<div>
<button disabled={!hasNextPage} onClick={() => fetchNextPage()}>
LoadMore
</button>
</div>
<div>{isFetching && !isFetchingNextPage ? "Fetching..." : null}</div>
</div>
);
};
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);
},
onSettled() {
/* ... */
},
});
const onCreateTodo = (e) => {
e.preventDefault();
mutation.mutate({ title });
};
return <>...</>;
};
mutate 메서드를 이용해서 요청 함수를 호출할 수 있다.onSuccess, onError 메서드를 통해 성공 했을 시, 실패 했을 시 response 데이터를 핸들링할 수 있다.onMutate는 mutation 함수가 실행되기 전에 실행되고, mutation 함수가 받을 동일한 변수가 전달된다.onSettled는 try...catch...finally 구문의 finally처럼 요청이 성공하든 에러가 발생되든 상관없이 마지막에 실행된다.const mutation = useMutation(addTodo);
try {
const todo = await mutation.mutateAsync(todo);
console.log(todo);
} catch (error) {
console.error(error);
} finally {
console.log("done");
}
mutateAsync를 사용해서 얻어올 수 있다.💡 mutate와 mutateAsync는 무엇을 사용하는게 좋을까?