서버 상태 가져오기
캐싱
동기화 및 업데이트
등 을 쉽게 다룰 수 있도록 도와주는 라이브러리.
클라이언트상태와 서버상태를 명확히 구분하기 위해서 만들어졌다.
- 캐싱
- 동일한 데이터 중복 요청을 단일 요청으로 통합
- 백그라운드에서 오래된 데이터 업데이트
- 데이터 상태 확인 (오래된 상태, 업데이트상태-빠르게 반영)
- 페이지네이션, 데이터 지연 로드 와 같은 성능 최적화(페이지네이션,인피니티스크롤)
- 서버상태의 메모리 및 가비지 수집 관리
- 구조 공유를 사용하여 쿼리 결과 메모화
import {QueryClient} from '@tanstack/react-query'
//기본 옵션
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: Infinity,
// ...
},
},
});
function App() {
return (
// react-query연결
<QueryClientProvider client={queryClient}>
<App/>
</QueryClientProvider>;
);
}
caching
: 특정 데이터의 복사본을 저장하여 이후 동일한 데이터의 재접근 속도를 높이는 것fresh
: 새롭게 추가된 쿼리 인스턴스이며 만료되지 않은 쿼리.(컴포넌트가 마운트, 업데이트 되어도 데이터 재요청을 하지 않고 항상 캐시된 데이터를 가져온다.)fetching
: 요청 상태인 쿼리 (대기중)stale
: 데이터 패칭이 완료되어 만료된 쿼리 (컴포넌트가 마운트, 업데이트 되면 캐시된 데이터가 환횐다.)inactive
: 비활성화된 쿼리(5분뒤 가지비 콜렉터가 캐시를 제거한다.)
fetch
),가져오는 데이터는 A라는 queryKey로 캐싱함fresh
) 상태에서 staleTime
(기본값 0) 이후 오래된 (stale
)상태로 변경됨cacheTime
(기본 5분)만큼 유지하다가 가비지 컬렉션 됨cacheTime
이 지나기전, A 쿼리 인스턴스가 신선한(fresh
)상태라면 새롭게 mount 되면 캐시된 데이터 보여줌기본값으로 설정된 경우 오래된 쿼리는 자동으로 데이터를 다시 가져온다.
// 방법1
const {data,isLoading,...} = useQuery(queryKey,queryFn,{
//options
}
//방법2
const result = userQuery({
queryKey,
queryFn,
{// option
}
})
result.data
result.isLoading
// 예제
const getAllSuperHero = async ()=>{
return await axios.get('http://localhost:4000/superheros')
}
const {data, isLoading} = useQuery(['super-heros'],getAllSuperHeor)
const getSuperHero = async ({queryKey}:any) =>{
const heroId = queryKey[1]
return await axios.get(`http://localhost:4000/superheroes/${heroId}`)
}
const useSuperHeroData = (heroId:string)=>{
return useQuery(['super-hero', heroId] ,getSuperHero)
}
const getSuperHero = async (heroId: string) => {
return await axios.get(`http://localhost:4000/superheroes/${heroId}`);
};
const useSuperHeroData = (heroId: string) => {
return useQuery(["super-hero", heroId], () => getSuperHero(heroId));
};
const { status, isLoading, isError, error, data, isFetching, ... } = useQuery(
["colors", pageNum],
() => fetchColors(pageNum)
);
status
: 상태를 표현하는 4가지 값
- idle: 쿼리 데이터가 없고 비었을때
- loading : 캐시된 데이터가 없고 로딩중
- error: 요청 에러
- success: 요청성공
data
: 쿼리 함수가 리턴한 Promise에서 resolve된 데이터isLoading
: 캐싱 된 데이터가 없을때, 즉,처음 실행된 쿼리 일때 로딩 여부에 따라 true/false 로 반환isFetching
:캐싱 된 데이터가 있더라도 쿼리가 실행되면 로딩 여부에 따라 true/false로 반환된다.error
: 쿼리 함수에 오류가 발생한 경우, 쿼리에 대한 오류 객체isError
: 에러가 발생한 경우 trueconst {isLoading, isFetching, data, isError, error} = useQuery(['super-hero'], getSuperHero,{
retry:10
}
query/mutation 작업이 실패하면 자동으로 재시도를 한다. Query 기본값은 3번 이며, Mutation의 기본값은 0이다. retryDelay설정을 통해 간격을 설정 할 수 있다.
const {isLoading, isFetching, data, isError, error} = useQuery(['super-hero'], getSuperHero,{
cacheTime: 5*60*1000
staleTime: 1*60*1000
}
데이터가 오래된 것으로 인식하게 되는 시간.(fresh -> stale) ms로 저장되는데 기본값 은 0 이며 데이터가 오래 되었다고 판단되면 다시 데이터를 가지고 온다.
데이터를 얼마나 오랫동안 보관 할 것인지 나타내는 시간. ms단위로 저장되는데 기본값 은 5분(5 x 60 x 1000). 쿼리 인스턴스가 unmount 되면 데이터는 비활성화(inactive) 상태가 되는데, 비활성화 된 데이터는 cacheTime에 설정된 시간이 지난후 가비지 컬렉션이된다.
const {isLoading, isFetching, data, isError, error} = useQuery(['super-hero'],
getSuperHero,{
refetchOnMount: true
}
데이터가 stale 상태일 경우, mount마다 refetch를 실행하는 옵션 기본값 true
const {isLoading, isFetching, data, isError, error} = useQuery(['super-hero'],
getSuperHero,{
refetchOnWindowFocus: true
}
데이터가 stale 상태일 경우 윈도우 포커싱 될때마다 refetch 옵션 기본값 true
const {isLoading, isFetching, data, isError, error} = useQuery(['super-hero'],
getSuperHero,{
refetchInterval:2000,
refetchIntervalInbackground:true
}
리얼타임 웹을 위한 기법으로 일정한 주기(특정시간)를 가지고 서버와 응답을 주고 받는 방식
refetchInterval
: ms(시간)을 값으로 넣어주면 일정 시간 마다 자동으로 refetch refetchIntervalInBackground
: refetchInterval과 함께 사용하는 옵션, 탭/창이 백그라운드에 있는 동안 refetch(브라우저에 focus되지 않아도 refetch 시켜준다.)const {isLoading, isFetching, data, isError, error, refetch} = useQuery(['super-hero']
,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</button>
</div>
)
쿼리가 자동으로 실행되지 않도록 할 때 설정. false를 주면 자동으로 실행되지 않는다. useQuery리턴 데이터 중 status가 idle 상태로 시작한다.
enabled:false
: queryClient가 쿼리를 다시가져오는 방법 중 invalidateQueries와 refetchQueries를 무시한다.const { isLoading, isFetching, data, isError, error, refetch } = useQuery(
["super-hero"],
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>
);
쿼리 함수에서 반환된 데이터의 일부를 변환하거나 선택할 수 있다.
const fetchColors = async (pageNum:number) =>{
return await axios.get(`http://localhost:4000/colors_limit=2&_page=${pageNum}`)
}
const {isLoading, isError, error, data, isFetching, isPreviousData}= useQuery(['colors', pageNum],
()=>fetchColors(pageNum),{
keepPreviousData: true
})
const queryResults = useQueries({
queries:[
{
querykey: ['super-hero',1],
queryFn : () => fetchSuperHero(1),
staleTime:Infinity,
},
{
querykey:['super-hero',2]
queryFn : () => fetchSuperHero(2),
staleTime:0,
}
]
})
QueryClient 인스턴스는 React query에 유용한 기능을 담고 있다.
import {useQueryClient} from '@tanstak/react-query'
const Todos = ()=>{
const QueryClient = userQueryClient()
}
쿼리를 무효화 하여 데이터를 다시 가져오게 할 수 있다.
import {useQueryClient} from '@tanstak/react-query'
const Todos = ()=>{
const QueryClient = userQueryClient()
const mutation = useMutation({
mutationFn : addTodo,
onSuccess(data) {
queryClient.invalidateQueries(["todo"]); // 이 key에 해당하는 쿼리가 무효화!
console.log(data);
},
onError(err) {
console.log(err);
},
})
}
const query = useQuery(["super-heroes"], {
/* ...options */
});
const queryClient = useQueryClient();
const onCancelQuery = (e) => {
e.preventDefault();
queryClient.cancelQueries(["super-heroes"]);
};
return <button onClick={onCancelQuery}>Cancel</button>;
const queryClient = useQueryClient()
//예제1
useMutation(addSuperHero, {
onSuccess(data) {
queryClient.setQueryData(["super-heroes"], (oldData: any) => {
return {
...oldData,
data: [...oldData.data, data.data],
};
});
},
onError(err) {
console.log(err);
},
});
//예제2 낙관적 업데이트
useMutation({
mutaitionFn: uptateTodo,
onMutate : async(newTodo) =>{
//optimistic update 한것이 덮어써지지 않도록 쿼리 취소
await queryClient.cancelQueries({querykey:['todo']})
// 에러 발생시 복원을 위해 기존 데이터 저장
const previousTodos = queryClient.getQueryData(['todos'])
//예상되는 변경 값으로 업데이트
queryClient.setQueryData(['todos'],(old)=>[...old, newTodo])
return {previousTodos}
},
// mutation이 실패하면 onMutate에서 반환된 context를 사용하여 롤백 진행
onError:(err, newTodo, context)=>{
// context를 통해 기존 값 쿼리 업데이트
queryClient.setQueryData(['todos'], context.previousTodos)
}
// 오류 또는 성공 후에는 항상 리프레쉬
onSettled:()=>{
queryClient.invalidateQueries({queryKey:['todos']})
}
})
때때로 수동으로 쿼리를 업데이트 해 주는 것이 더 좋은 사용성을 제공할 수 있다.
qeuryClient.setQueryData를 사용하면 된다.
낙관적 업데이트(Optimistic Update)
: Mutation으로 상태를 변경후 데이터 노출이 늦어진다면 수동으로 먼저 쿼리를 업데이트 하여 사용자에게 빠르게 변경된 결과를 제공하는 것. (인터넷 속도가 느리거나 서버가 느릴 때이다. 유저가 행한 액션을 기다릴 필요 없이 바로 업데이트되는 것처럼 보이기 때문에 사용자 경험(UX) 측면에서 좋다.)const perfetchNextPosts = async (nextPage:number)=>{
const queryClient = useQueryCleint();
await queryClient.prefetchQuery(
['posts',nextPage],
()=>fetchPost(nextPage),
{
//options...
}
)
}
useEffect(()=>{
const nextPage = currentPage +1
if(netxtPage < maxPage){
prefetchNextPosts(nextPage)
}
},[currentPage])
미래에 사용 될 수 있는 쿼리를 미리 가져올 수 다. queryClient.prefetchQuery
로 가져오는 데이터가 이미 캐싱되어 있다면 데이터를 가져오지 않는다.
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 <>...</>;
};
onMutate
:mutation 함수가 실행되기 전에 실행되고, mutation 함수가 받을 동일한변수가 전달된다.onSuccess
, onError
: 성공시, 실패시onSettled
: 성공하든 에러가 발생되든 상관없이 마지막 실행mutation 과 mutationAsync 차이 (mutation 사용이 좀더 유리함.)
- mutation : onSuccess,onError,onSettled 와 같은 콜백 함수로 처리 할수 있다.
- mutationAsync: Promise 반환하기 때문에 then,Async await로 처리해야 한다.(에러핸들링을 직접 다뤄야 한다.)
참고
https://beomy.github.io/tech/react/tanstack-query-v4/#errorboundary
https://github.com/ssi02014/react-query-tutorial#infinite-queries
좋은 글이네요. 공유해주셔서 감사합니다.