Server State 와 Client State의 분리를 목적으로 만들어짐
공식 문서
react-query를 사용하기 전 axios로 api를 요청하는 함수만 만들어놓고 컴포넌트에서 불러오는 방식을 사용하고 있었다. 물론 전역적인 상태관리도 안되고 있었기 때문에 redux사용에 대한 고민을 많이 했었는데 현재 만드는 앱의 규모에서 redux를 사용하기에는 작성되는 코드의 양에 비해 필요성이 느껴지지 않았다. (물론 이건 내가 redux를 제대로 사용할줄 몰라서 그런걸수도 있다...) 고민하던 중에 react-query를 알게 되고 더 간편하게 캐싱이나 전역관리가 될수 있을 것 같아 도입하게 되었다.
캐싱(유지)되지 못하고 사용자가 컴포넌트를 나갈때 데이터가 사라지며 다시 돌아왔을경우 또 다시 데이터로딩을 해야한다.
캐싱을 원한다면 데이터를 Context 또는 리덕스, 리코일 등의 라이브러리에서 관리해야 하는데 이렇게 되면 준비해야 할 코드가 많다(redux-thunk, redux-saga등 미들웨어사용)
-> 리액트 쿼리를 사용하면 컴포넌트에서 Hook을 기반으로 데이터 로딩을 훨씬 편하게 할 수 있고, 캐싱도 기본적으로 제공하여 쉽게 구현할 수 있다.
import {useQuery} from 'react-query';
function Sample(){
const result = useQuery('articles',getArticles);
const {data,error,isLoading} = result;
}
//isLoading : loading상태 true인지 false인지
//data : 요청 성공한 데이터
키
를 넣는다.키
가 같을경우 기존의 데이터를 보여줌배열
,객체
,문자열
이 가능한데 배열의 경우 순서가 중요, 객체에서는 순서는 상관없다 문자열
로 넣을경우 ['문자열']과 같음
queryKey
를 배열로 넣을경우 queryFn에서 prameter로 받을 수 있다
unique key 활용
const result = useQueries([
{
queryKey: ["getRune", riot.version],
queryFn: () => api.getRunInfo(riot.version)
},
{
queryKey: ["getSpell", riot.version],
queryFn: () => api.getSpellInfo(riot.version)
}
]);
useEffect(() => {
console.log(result);
// [{rune 정보, data: [], isSucces: true ...}, {spell 정보, data: [], isSucces: true ...}]
const loadingFinishAll = result.some(result => result.isLoading);
console.log(loadingFinishAll); // loadingFinishAll이 false이면 최종 완료
}, [result]);
서버에서 온 데이터를 수정해야 한다면 mutation을 추천
react query 사용법 및 쓰는 이유(블로그)
1. 쿼리 데이터 무효화로 리랜더링 실행 (Invalidation)
Invalidation
invalidateQueries matching
import { useMutation, useQueryClient } from 'react-query'
const queryClient = useQueryClient()
// When this mutation succeeds, invalidate any queries with the `todos` or `reminders` query key
const mutation = useMutation(addTodo, {
onSuccess: () => {
queryClient.invalidateQueries('todos')
queryClient.invalidateQueries('reminders')
},
})
2. optimistic update
optimistic update란 서버와의 통신이 실패하던지 성공하던지 낙관적으로 바라보고 성공을 가정하고 쿼리데이터를 업데이트 하는것
const queryClient = useQueryClient()
useMutation(updateTodo, {
// When mutate is called:
onMutate: async newTodo => {
// overwrite하지 않게 기존 쿼리 데이터는 취소 한다
await queryClient.cancelQueries('todos')
// 이전 쿼리 데이터를 저장(서버 통신에 실패할경우 이 데이터로 돌리기 위해)
const previousTodos = queryClient.getQueryData('todos')
// 새 값으로 쿼리 업테이트
queryClient.setQueryData('todos', old => [...old, newTodo])
// 이전 쿼리 값은 return한다
return { previousTodos }
},
// 만약 mutation에 실패하면 이전 쿼리 데이터로 rollback
onError: (err, newTodo, context) => {
queryClient.setQueryData('todos', context.previousTodos)
},
// error나 success일때 항상 쿼리데이터를 referch
onSettled: () => {
queryClient.invalidateQueries('todos')
},
})
queryClient.cancelQuerie
: 발신 쿼리를 취소하는 데 사용할 수 있다. (optimistic update를 덮어쓰지 않도록)queryClient.getQueryData
: 기존 쿼리의 상태를 가져오는 동기 함수. 쿼리가 존재하지 않으면 undefined
를 반환.queryClient.setQueryData
: 쿼리의 캐시된 데이터를 즉시 업데이트할 수 있는 동기 함수. 쿼리가 존재하지 않으면 생성된다.무한 스크롤 이용시 유용한 hook
useInfiniteQuery(key
, queryFn
, { getNextPageParam
: 값이 undefined가 되기 전까지 fetch })
데이터를 가져오면 data.pages의 배열 마지막에 가져와 진다. 즉 객체 하나의 데이터를 가져오는게 아니라면 [{results:[array],...},{result:[array]},...]이 되어 버림
-> 해결방안 : flatMap을 이용하면 배열속의 값들을 하나의 배열로 다시 만들 수 있다.
flatMap
참고한 stackOverFlow
const getNextPageParam = (lastPage, pages)=>{
const nextPage = pages.length
return lastPage.length < GET_SIZE ? undefined : nextPage
}
<button onClick={() => fetchNextPage()}>
infinite query가 invalidate될때 어떻게 작동하는지
: ## What happens when an infinite query needs to be refetched?
redux를 이용하는 것과 달리 reatQuery이용시 어디에서 api를 부르는지 모를수 있음... (컴포넌트에 유착되는 등) 이에 대한 해결책 필요...
참고
# Store에서 비동기 통신 분리하기 (feat. React Query)
공식문서 번역 블로그
https://kyounghwan01.github.io/blog/React/react-query/basic/#%E1%84%89%E1%85%A1%E1%84%8B%E1%85%AD%E1%86%BC%E1%84%92%E1%85%A1%E1%84%82%E1%85%B3%E1%86%AB-%E1%84%8B%E1%85%B5%E1%84%8B%E1%85%B2