[ TIL ] 2022-05-16

Gorae·2022년 5월 16일
0
post-thumbnail

React Query

참고 1 / 참고 2 / 참고 영상

❤️‍🔥 react-query 로 캐싱하기

useQuery 훅의 파라미터를 통해 API 데이터의 만료 시간, 리프레시 간격, 캐싱된 데이터의 유지 기간, 브라우저 포커스시 데이터 리프레시 여부, 성공 or 에러 콜백 등 다양한 기능을 제어할 수 있다.

✨ react-query 의 캐싱은 stale 과 cachetime 을 통해 이루어진다

stale

  • stale
    외부 요청으로 서버 데이터가 변경될 때, 내 브라우저가 가진 데이터는 이미 오래된 데이터가 되었으므로(받았을 당시의 snapshot), 이 상태를 stale 하다고 한다.
    리액트는 캐싱된 데이터의 상태를 stale(탁한, 낡은)로 여기고, stale 한 상태의 경우 아래의 조건을 만족하면 refetch 된다

    1. 새로운 query instance 가 마운트될 때(page 이동 후 다시 왔을 때)
    2. 브라우저 화면을 이탈했다가 다시 focus 할 때
    3. 네트워크가 끊겼다가 다시 연결될 때
    4. 특별히 설정한 refetch interval 에 의한 경우
  • query 에 별다른 action 이 없으면( 활성화된 useQuery, useInfiniteQuery 인스턴스가 없는 쿼리 결과 ) inactive 상태로 캐시에 있다가, 5분 뒤에 메모리에서 사라진다. cacheTime 옵션을 통해 이 시간을 조정할 수 있다

  • 백그라운드에서 3회 이상 실패한 쿼리는 에러 처리된다. retry 옵션을 통해 오류 발생 시 재시도 할 횟수를 정할 수 있고, retryDelay 옵션으로 재시도 대기 시간을 설정할 수 있다.

  • refetchOnWindowFocus 등의 옵션으로 refetch 설정을 막을 수 있고, staleTime 옵션으로 설정한 시간 동안 stale 되지 않도록 하여 refetch 를 막을 수도 있다

  • staleTime

    • 데이터가 fresh -> stale 상태로 변경되는 데 걸리는 시간으로, defaultValue 는 0 이다. 고로, staleTime 을 지정해주지 않는다면, react-query 의 캐싱 기능을 제대로 사용할 수 없다는 말이다. 받아오는 즉시 stale 상태인 것.
    • fetch 되고 나서 staleTime 이 지나지 않았다면, unmount 후 mount 되어도 fetch 가 일어나지 않는다.
    • fresh 상태일 땐 쿼리 인스턴스가 새롭게 mount 되어도, fetch 가 일어나지 않는다

cacheTime

  • cacheTime
    • 데이터가 inactive 상태일 때 캐싱된 상태로 남아있는 시간으로, defaultValue 는 5분이다. 캐시 구조에 저장된 데이터는 메모리 상에 존재하게 된다
    • cacheTime 이 지나기 전에 쿼리 인스턴스가 다시 mount 되면, 데이터를 fetch 하는 동안 캐시 데이터를 보여준다
    • cacheTime 은 staleTime 과 관계 없이, 무조건 inactive 시점을 기준으로 캐시 데이터 삭제를 결정한다. 즉, staleTime 이 길어도 cacheTime 이 짧다면 데이터는 사라진다
    • cacheTime 이 지나면, 데이터는 가비지 콜렉터로 수집된다

staleTime, cacheTime 은 useQuery 의 옵션으로 설정한다

const { data } = useQuery('users', getUsers, {
  // options
  staleTime: 5000,
  cacheTime: Infinity,
})

react-query 는 캐시 관리를 위해 QueryClient 인스턴스를 사용한다

  • react-query 로 캐시 관리를 하기 위해서는, 컴포넌트가 useQuery 훅 안에서 QueryClient 인스턴스에 접근할 수 있도록, QueryClientProvider 를 컴포넌트 트리 상위에 추가해줘야 한다
import { QueryClient, QueryClientProvider } from 'react-query'

const queryClient = new QueryClient() // 인스턴스 생성

root.render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
  </React.StrictMode>
)
  • 그리고 useQuery 로 서버 데이터를 가져올 수 있다
    • queryKey 에는 문자열과 배열을 넣을 수 있고, 이 유연함이 캐싱 처리를 쉽게 해준다. 쿼리 키가 다르면, 캐싱 관리도 별도로 하기 때문이다.
    • queryFunction 에는 서버 데이터를 요청하고, Promise 를 리턴하는 함수를 전달한다. axios.get(...), fetch(...) 등의 함수.
const { data, isLoading } = useQuery(queryKey, queryFunction, options)
// 객체 필드의 내용이 같으면 순서가 달라도 같은 키로 취급한다
useQuery(['todo', { preview: true, status: 'done' }], ...)
useQuery(['todo', { status: 'done', preview: true }], ...)
// 객체 필드의 값이 다르면 다른 키로 취급한다
useQuery([{ preview: true, status: 'done' }], ...)
useQuery([{ preview: false, status: 'done' }], ...)
  • 캐싱 데이터의 조회는 useQueryClient 에 옵션을 설정하여 활용한다
const queryClient = new QueryClient({
	defaultOptions : {
    	queries : {
        	refetchOnWindowFocus : false,
            refetchOnMount : false,
            retry : false
        }
    }
})
  • useQuery 의 refetch 함수

    • 캐싱 결과는 조회하지 않고 무시한 채, 요청을 날리는 메서드. QueryClient 객체에 저장된 캐싱 내용에 해당 요청값의 키가 존재한다 하더라도, re-fetch 요청을 진행한다.
    • 결론적으로, 캐싱 구현을 하기 위해서는 enabled 옵션을 false로 두면 안된다.
  • useQuery 에서 'enabled: ture' 상태와 캐싱을 둘 다 구현하려면?

    1. enabled option 에 대해, 특정 상태를 충족할 때만 true 로 만든다. 그 외에는 false 상태를 유지하여 refetch 메서드 강제 호출을 막는다.
    2. enabled true 일 때 요청을 성공하게 되면, 값이 캐싱될 것이다
  • react-query/devtools

    react-query 를 설치할 때 함께 받아지는 것으로, 따로 설치할 필요가 없다. 페이지 루트에 가까울수록 더 잘 동작한다고 함. production 모드일 땐 번들에 포함되지 않으므로, 신경쓰지 않고 개발하면 된다.

import { ReactQueryDevtools } from 'react-query/devtools'

  ...
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
)

CORS 에러

공공 데이터는 처음 받아 보는데, " 데이터 요청 시 공공데이터 API 앞에 "https://cors-anywhere.herokuapp.com/"을 작성하기 " 같은 방식으로 CORS 에러가 해결될 수 있다는 걸 알았다. 배포 시에도 문제없는 방법일지는 해 봐야 알 것 같다.

공공 데이터 포털
참고 블로그

Git

  • git squash
    여러 개의 커밋을 하나로 합칠 수 있음. 자잘한 커밋으로 지저분해졌을 때 유용할 것이다.
  • git rebase
    '기준을 여기로 하겠다' 정도의 의미인 줄 알았는데, 각 브랜치 커밋을 시간순서대로가 아니라, 브랜치를 기준으로 묶어서 나열하는 것이었다.

참고 블로그

Tips

폴더 구조를 NavBar/NavBar.tsx 이런식으로만 만들었는데, NavBar/index.tsx 이렇게 만들면 import 길이가 줄어든다.

// NavBar/NavBar.tsx 일 경우
import NavBar from 'components/NavBar/NavBar'

// NavBar/index.tsx 일 경우
import NavBar from 'components/NavBar'

한 줄 일기

'이렇게 하면 되지 않을까요? 해 볼까요?' 말고, '이렇게 하시면 될 겁니다' 하는 사람이고 싶다

profile
좋은 개발자, 좋은 사람

0개의 댓글