React Query
Fetch, cache and update data in your React and React Native applications all without touching any "global state".
이것이 리액트 쿼리 홈페이지 첫번째에 나와있는 말이다.
https://velog.io/@___pepper/React-Global-State-%EA%B4%80%EB%A6%AC
리액트 쿼리는
리액트에서 비동기 로직을 부드럽게 다룰 수 있게 해주는 라이브러리다.
state관리에 아주 효율적이다.
기존에 isLoading, isError, refetch, 데이터 캐싱 등 직접 구현하기 귀찮고 번거로운 작업을 간단하게 만들어준다.
즉 서버 상태를 관리하기에 최고의 라이브러리 중 하나이다.
크게 구성하기 어렵지 않으면서 즉시 사용이 가능해서 편리하다.
반응성이 좋다
잠재적으로 대역폭을 절약하고 메모리 성능을 높이는 데 도움이 된다.
사용법은 공식 홈페이지에 나와있는대로 대단히 간단하다
import { QueryClient, QueryClientProvider, useQuery } from 'react-query'
const queryClient = new QueryClient()
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Example />
</QueryClientProvider>
)
}
먼저 상위 함수
app.tsx나 index.tsx 에다가
위와 같이 설정해 준다.
function Example() {
const { isLoading, error, data } = useQuery('repoData', () =>
fetch('https://api.github.com/repos/tannerlinsley/react-query').then(res =>
res.json()
)
)
if (isLoading) return 'Loading...'
if (error) return 'An error has occurred: ' + error.message
return (
<div>
<h1>{data.name}</h1>
<p>{data.description}</p>
<strong>👀 {data.subscribers_count}</strong>{' '}
<strong>✨ {data.stargazers_count}</strong>{' '}
<strong>🍴 {data.forks_count}</strong>
</div>
)
}
그리고 사용하고 싶은 곳에 위와 같이 설정해주면 된다.
useQuery
import { useQuery } from "react-query";
const { data, isLoading, error } = useQuery(queryKey, queryFn, options)
// 이 3가지 보다 더 많다 하지만 이 3가지를 많이 사용한다.
Querykey
QueryKey 를 기반으로 데이터 캐싱을 관리한다.
문자열 또는 배열로 지정할 수 있다.
// 문자열
useQuery('todos', ...)
// 배열
useQuery(['todos'], ...)
Query Functions
useQuery에 들어가는 두 가지 인자에 대해 알아볼 것이다.
첫 번째 인자는 queryKey이다.
이건 우리 query의 고유 식별자 이다. 즉 query를 지칭할 이름이라고 생각하면 된다.
두 번째 인자는 fetcher함수이다.
useQuery에 함수를 전달 할때는 반드시 함수 자체를 넘겨줘야한다.
여기서 두 가지 주의 할 점이 있다.
첫 번째는 queryKey는 고유하게 만들어 줘야한다.
queryKey가 중복이되면 후자가 전자를 덮어 버리기 때문에 data가 유실 될 수 있다.
우리가 프로젝트를 할때 useQuery를 딱 한 번만 사용할게 아니라면
변수 같을 것을 지정할 때 추가적으로 네이밍을 해주는 게 좋다
예를 들어
이런식으로 변수를 지정할때 id를 많이 사용하는데 id를 기준으로 서로다른 fetch함수를 사용하지만
queryKey가 같아져서 데이터가 유실 될 수 가 있다.
그래서 아래처럼
이렇게 네이밍을 고유하게끔 해주면 된다.
그리고 여기서 질문 할 수 있다.
왜 갑자기 배열을 사용하는 거죠 ?
그 이유는 React query는 key를 array로 감싸서 표현을 한다.
따라서 단독으로 쓰면 굳이 array로 만들어 줄 필요가 없지만
이렇게 따로 네이밍을 한다면 반드시 array로 만들어줘야한다.
안그러면 useQuery는 쉼표단위로 인자를 받기때문에 오류가 뜬다.
마지막으로 또 다른 질문을 하실 수도 있다.
바로 왜 에로우 함수를 사용하느냐 이다
변수를 사용할때는 이런식으로 해줘야한다.
왜냐하면 위에 예제에 본 함수를 이렇게 fetchCoinInfo(coinId) 작성하면 함수가 실행된 return값을 useQuery가 인자로 받기에
전혀 다른 결과가 나온다.
따라서 함수 자체를 보내기 위해 에로우 함수를 사용하면 좋다.
만약 전달할 인자가 없으면 fetchCoinInfo 이렇게 만 써주면 된다.
useQuery는 아래와 같이 사용할 수 있다.
주로 첫 번째와 두 번째처럼 한다.
useQuery('todos', fetchTodos);
useQuery(['todos', todoId], () => fetchTodoById(todoId));
useQuery(['todos', todoId], async () => {
const data = await fetchTodoById(todoId);
return data
});
useQuery(['todos', todoId], ({ queryKey }) => fetchTodoById(queryKey[1]));
자 이제는 useQuery가 return 하는 것들을 알아보자
먼저 isLoading이다.
isLoading은 true 혹은 false인 boolean 이다
IsLoading은 fetcher함수가 작동중이라면 true를 반환하고 작동이 끝났다면 false를 반환한다.
그 다음은 data이다.
fetchCoins가 끝나면 react Query는 그 함수의 데이터를 data에 넣어줄 것이다.
정리하자면
1. useQuery hook이 나의 fetcher함수를 부르고
2. 함수를 부르는 동안 isLoading은 false를 반환하다가
3. fetcher함수가 작동이 끝나면 isLoading이 true를 반환하면서 api에서 가져온 json자료를 data에 넣어둔다.
Query Options
자주 사용하는 것만 다뤄봄
enabled (boolean)
- enabled 는 쿼리가 자동으로 실행되지 않게 설정하는 옵션이다.
- 아래의 코드는 id가 존재할 때만 쿼리 요청을 한다는 의미의 코드이다.
const { data } = useQuery(
['todos', id],
() => fetchTodoById(id),
{
enabled: !!id,
}
);
retry (boolean | number | (failureCount: number, error: TError) => boolean)
- retry 는 실패한 쿼리를 재시도하는 옵션이다.
- 기본적으로 쿼리 실패시 3번 재시도 한다.
- true 로 설정하면 쿼리 실패시 무한 재시도하고 false로 설정하면 재시도를 하지 않는다.
staleTime (number | Infinity)
- staleTime 은 데이터가 fresh 상태로 유지되는 시간이다. 해당 시간이 지나면 stale 상태가 된다.
- default staleTime은 0 이다.
- fresh 상태에서는 쿼리가 다시 mount 되어도 fetch가 실행되지 않는다.
cacheTime (number | Infinity)
- cacheTime 은 inactive 상태인 캐시 데이터가 메모리에 남아있는 시간이다. 이 시간이 지나면 캐시 데이터는 가비지 컬렉터에 의해 메모리에서 제거된다.
- default cacheTime 은 5분이다.
refetchOnMount (boolean | "always")
- refetchOnMount 는 데이터가 stale 상태일 경우 마운트 시 마다 refetch를 실행하는 옵션이다.
- default true
- always 로 설정하면 마운트 시 마다 매번 refetch 를 실행한다.
refetchOnWindowFocus (boolean | "always")
- refetchOnWindowFocus 는 데이터가 stale 상태일 경우 윈도우 포커싱 될 때 마다 refetch를 실행하는 옵션이다.
- 예를 들어, 크롬에서 다른 탭을 눌렀다가 다시 원래 보던 중인 탭을 눌렀을 때도 이 경우에 해당한다. 심지어 F12로 개발자 도구 창을 켜서 네트워크 탭이든, 콘솔 탭이든 개발자 도구 창에서 놀다가 페이지 내부를 다시 클릭했을 때도 이 경우에 해당한다.
- default true
- always 로 설정하면 항상 윈도우 포커싱 될 때 마다 refetch를 실행한다는 의미이다.
예제 코드!
// 유저 정보를 가져오는 쿼리이다.
const { data: userInfo } = useQuery(
['user'],
getUser,
{
refetchOnWindowFocus: true,
staleTime: 60 * 1000, // 1분
}
)
- QueryClient defaultOptions 설정으로 refetch 기능들을 다 false로 꺼버렸을 경우에는 refetch 기능이 실행되지 않는다. 그럴 경우엔 refetchOnWindowFocus 옵션이 실행되게끔 true로 설정하면 된다.
- fresh 상태인 1분 동안은 아무리 다른 탭을 왔다갔다해도 fetch 요청을 하지 않는다.
refetchOnReconnect (boolean | "always")
- refetchOnReconnect 는 데이터가 stale 상태일 경우 재 연결 될 때 refetch를 실행하는 옵션이다.
- default true
- always 도 위에 두 옵션 처럼 쿼리가 매번 재 연결될 때 마다 refetch를 실행한다.
onSuccess ((data: TDdata) => void)
- onSuccess 는 쿼리 성공 시 실행되는 함수이다.
- 매개변수 data는 성공 시 서버에서 넘어오는 response 값이다.
onError ((error: TError) => void)
- onError 는 쿼리 실패 시 실행되는 함수이다.
- 매개변수로 에러 값을 받을 수 있다.
예제 코드
const { data: userInfo } = useQuery(
['user'],
getUser,
{
refetchOnWindowFocus: true,
staleTime: 60 * 1000, // 1분
onError: (error) => {
if (error.response?.data.code === 401) {
//...
}
},
}
)
공식문서 한글화 더 공부할 것 ..
병렬 쿼리 (Parallel Queries)
"병렬" 쿼리는 병렬로 실행되거나 동시에 가져오기 동시성을 최대화하기 위해 실행되는 쿼리입니다.
수동 병렬 쿼리
병렬 쿼리의 수가 변경되지 않으면 병렬 쿼리를 사용하기 위한 추가 노력이 없습니다.
원하는 만큼의 react Query useQuery와 useInfiniteQuery후크를 나란히 사용하세요!
function App () {
// The following queries will execute in parallel
const usersQuery = useQuery('users', fetchUsers)
const teamsQuery = useQuery('teams', fetchTeams)
const projectsQuery = useQuery('projects', fetchProjects)
...
}
동적 병렬 쿼리
function App({ users }) {
const userQueries = useQueries(
users.map(user => {
return {
queryKey: ['user', user.id],
queryFn: () => fetchUserById(user.id),
}
})
)
}
종속쿼리
종속(또는 직렬) 쿼리는 실행하기 전에 완료해야 하는 이전 쿼리에 의존한다.
enabled 쿼리 옵션을 사용하는 것만 하면 될 정도로 쉽다.
// Get the user
const { data: user } = useQuery(['user', email], getUserByEmail)
const userId = user?.id
// Then get the user's projects
const { isIdle, data: projects } = useQuery(
['projects', userId],
getProjectsByUser,
{
// The query will not execute until the userId exists
enabled: !!userId,
}
)
// isIdle will be `true` until `enabled` is true and the query begins to fetch.
// It will then go to the `isLoading` stage and hopefully the `isSuccess` stage :)
Background Fetching Indicators
status === 'loading' 는 로딩상태를 보여주기에는 좋지만,
쿼리가 백그라운드에서 다시 가져오고 있다는 추가 표시기를 표시할 수 없습니다.
쿼리가 백그라운드에서 다시 가져온다는 것을 보여주기 위해서는
isFetching 을 사용하면 됩니다.
function Todos() {
const { status, data: todos, error, isFetching } = useQuery(
'todos',
fetchTodos
)
return status === 'loading' ? (
<span>Loading...</span>
) : status === 'error' ? (
<span>Error: {error.message}</span>
) : (
<>
{isFetching ? <div>Refreshing...</div> : null}
<div>
{todos.map(todo => (
<Todo todo={todo} />
))}
</div>
</>
)
}
Displaying Global Background Fetching Loading State
개별 쿼리 로드 상태 외에도 쿼리가 가져올 때(백그라운드에서 포함) 글로벌 로드 표시기를 표시하려면
you can use the useIsFetching hook
import { useIsFetching } from 'react-query'
function GlobalLoadingIndicator() {
const isFetching = useIsFetching()
return isFetching ? (
<div>Queries are fetching in the background...</div>
) : null
}
Window Focus Refetching
사용자가 애플리케이션을 떠나 오래된 데이터로 돌아오면 React Query는 백그라운드에서 자동으로 새로운 데이터를 요청합니다 . 다음 refetchOnWindowFocus옵션을 사용하여 전역적으로 또는 쿼리별로 비활성화할 수 있습니다 .
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
},
},
})
function App() {
return <QueryClientProvider client={queryClient}>...</QueryClientProvider>
}
이 옵션을 활용할때는 보통 이렇게 쓰입니다.
useQuery('todos', fetchTodos, { refetchOnWindowFocus: false })
더 공부해야 할것 들 .......
캐싱
react-query의 캐싱 개념은 stale와 cachetime을 통해 이루어진다.
stale와 cacheTime 먼저 알아보자
stale 이란?
react query는 기본적으로 캐싱된 data를 stale한 상태로 여긴다.
stale의 사전적 의미는 "신선하지 않는" 이라는 뜻을 가진다.
즉 외부요청으로 서버 데이터가 변경되었다면 내 브라우저가 가진 데이터는 이미 오래된 낡은 데이터가 되었으므로 stale하다고 말하는 것이다.
stale이란 최신화가 필요한 데이터라는 의미로 stale한 상태가 되면 다음의 경우에 refetch 된다.
refech가 되는 조건
1. 새로운 query instance가 마운트 될 때 ( page를 이동 했다가 왔을 때 의미)
2. 브라우저 화면을 이탈 했다가 다시 focus 할 때
3. 네트워크가 다시 연결될 때
4. 특별히 설정한 refetch interval에 의한 경우 (refetchinterval)
refetchOnWindowFocus 옵션 등으로 기본 refetch 설정을 막을 수 있고
staleTime 옵션으로 설정한 시간 동안 데이터가 stale되지 않도록 해 refetch를 막을 수도 있다.
또한 query에 별다른 action이 없다면 Inactive 상태로 캐시에 남아 있다가 5분 뒤에 메모리에서 사라진다.
cacheTime 옵션을 설정해서 이 시간을 조정할 수 있다.
react query 캐싱
react query는 아무 옵션을 설정하지 않으면 캐싱되지 않는다.
캐싱을 제대로 쓰려면 staleTime와 cacheTime에 대해 알아야 한다.
staleTime
기본 값
defaultValue : 0
- 데이터가 fresh -> stale 상태로 변경되는데 걸리는 시간
- fresh 상태일때는 쿼리 인스턴스가 새롭게 mount 되어도 네트워크 fetch가 일어나지 않는다.
- 데이터가 한번 fetch 되고 나서 staleTime이 지나지 않았다면 unmount 후 mount 되어도 fetch가 일어나지 않는다.
전달받은 데이터는 react query의 자료구조 내용 중 cache에 저장이 된다.
이때, 이 캐시 데이터의 "신선한 상태"가 언제까지 될지를 말해주는 옵션이다.
받아오는 즉시 stale 하다고 판단하며 캐싱 데이터와 무관하게 계속해서 fetching을 수행한다.
여기서 주의할점이 있다.
1. staletime을 지정해주지 않고 쓴다면 react-query의 캐싱 기능을 제대로 활용할 수가 없다.
만약 해당 useQuery를 호출할 당시에 옵션으로 staletime을 따로 지정해주지 않았었다면, 항상 캐싱되어 있는 데이터는 stale하다고 여기기 때문에 refetching을 하게 되어 서버에 계속적인 요청을 하게된다.
만약 데이터 구조가 자주자주 변하는 어플리케이션이라면 지정하지 않는 편이 좋고 해당 브라우저에 표현되는 내용의 데이터들이 정적이라면 staletime을 지정해주고 요청하는 것이 서버의 부담을 경감시키는 것이 될 것이다.
2. enabled을 설정하여 retry 호출을 막자
옵션에 존재하는 enabled:false를 설정해놓을 경우, 초기 마운트시에 해당 useQuery가 마치 useEffect처럼 첫 마운트시 fetcher 함수 호출을 하고, 실패했을 때는 계속 retry를 하는 행위를 사전 차단할 수 있다.
그러나, enabled:false는 말 그대로 이 useQuery의 기능을 사용하지 않겠다고 말해주는 것과 같기 때문에 수동적으로 호출하는 방식이 필요하다. 그것이 바로 useQuery 함수 호출을 하여 리턴되는 객체에 포함되어 있는 refetch 함수다.
3. 진짜 중요한 refetch 함수의 개념
refetch 함수는 "캐싱 결과는 조회하지 않고 완전히 무시한 채로 그냥 ajax 요청을 날리는 메서드라는 점이다
refetch 함수는 캐싱 결과를 조회하지 않고 무시한다.
즉, 아무리 QueryClient 객체에 저장되어 있는 캐싱 내용에 해당 요청값의 키가 존재한다 하더라도,
그냥 무시해버리고 re-fetching 요청을 진행해버린다.
결론
캐싱을 구현하려고 한다면 enabled 옵션을 false로 두면 안된다.
그렇다면, true인 상태와 캐싱을 둘 다 구현하려면 어떻게 해야할까 ?
1. enabled option에 대해 특정한 상태를 충족할 때만 true로 만들고 그 외에는 false로 하여 초기 요청을 통한 retry로 오류를 생성하는 것을 막는다. (refetch 메서드로 강제 호출을 하지 않는다.)
2.그 뒤 조건부로 enabled가 true로 변경되면서 요청을 날리게 되어 성공하면,
data 프로퍼티에 그 값이 저장되고, 캐싱에도 저장이 될 것 이다.
출처 :
https://2ham-s.tistory.com/407
'React-JS > react' 카테고리의 다른 글
[React] React Hook Form (2) | 2023.10.22 |
---|---|
[React ] JSX 배열 반복문 사용하고 싶을때 (0) | 2022.03.18 |
react hook 정리 (0) | 2022.03.15 |
const에 대한 고찰 (0) | 2021.11.28 |