Frontend/TanStack Query

Tanstack Query, TkDodo 도아일체 (1)

Creative_Lee 2023. 12. 22. 15:20

https://tkdodo.eu/blog/practical-react-query

 

Practical React Query

Let me share with you the experiences I have made lately with React Query. Fetching data in React has never been this delightful...

tkdodo.eu

 

2018년경 GraphQL과 아폴로 클라이언트가 핫하던 시기에 사람들은 리덕스를 대체할 것이라며 난리법석이었다.

나는 전혀 이해하지 못했다 '데이터 패칭 라이브러리가 어떻게 전역상태 관리라이브러리인 Redux를 대체하지?'

 

아폴로는 데이터 패칭 기능뿐만 아니라, 서버 데이터 캐싱도 지원했다.

여러 컴포넌트에서 동일한 쿼리를 사용하면 캐싱된 데이터를 제공하는 기능이 있었다.

 

당시 사람들은 서버 데이터를 전역으로 사용하기 위해 리덕스를 사용하고 있었다.(불편함을 느끼면서도?)

자연스럽게 서버상태를 클라이언트 상태랑 별다를 것 없이 취급했었던 것 같다. 

생각해보니, 서버상태는 화면에 표시하는 것 외에는 딱히 사용하지 않더라.

 

화면에 표시할 상태를 서버 상태로 따로 분리한다고 하면, 막상 진짜 전역 상태는 몇 개 없더라.

그제야 사람들이 Redux를 대체할 수 있다며 열광했던 것을 이해했다.

 

나는 아폴로가 제공하는 로딩/오류 상태, 데이터를 가져오는 방식이 부러웠다.

REST API에도 비슷한 것이 있다면...


React query 기본옵션

쿼리 옵션의 기본값은 이미 완벽하게 설정해두었지만, 기호에 따라 커스텀할 수 있다.

또 사람들이 흔하게 실수하는 쿼리 옵션의 기본값에 대해 몇 가지 조언을 하려고 한다.

 

1. staleTime의 기본값은 0인데, 그렇다고 매 랜더링마다 queryFn을 호출하지 않는다!!

만약 예상치 못한 refetch가 발생한다면, 아마 refetchOnWindowFocus 때문일거다.

이 옵션 덕분에 사용자가 탭이동 후 복귀 했을 때, 로딩 상태 없이 자연스럽게 refetch를 시도한다.

데이터의 변경사항이 있다면 캐시와 화면 데이터를 업데이트 한다. 랜더링을 다시 한다는 말이지.

없다면 그대로 둔다.

2. gcTime과 staleTime 혼동하지 마라.

staleTime: 쿼리가 fresh -> stale 로 전환되기까지 시간이다.
쿼리가 fresh면 데이터는 캐시에서 가져오는거다. 네트워크 요청 없이!

쿼리가 stale 이여도 캐시의 데이터를 가져와 보여준다. but 너가 설정한 조건에 따라 백그라운드에서 refetch할 수도 있다.

 

gcTime: inactive 상태의 쿼리가 delete 로 전환되기까지 시간이다. gcTime이 시간이 지나면 캐시는 삭제된다.

해당 쿼리를 사용하는 모든 컴포넌트가 언마운트되면 inactive상태가 된다.

보통 옵션중 뭔가를 바꾸고 싶다는 생각이 들면, 대부분 staleTime일 거다.


query key를 dependency array 라고 생각해!

왜냐면 React query는 query key가 변경될 때 마다 refetch를 하거든!

 

자 생각해봐.

대부분의 경우에 너는 queryFn에 넘기려는 인자가 바뀔 때, 새로운 데이터를 불러오길 원할거야.

보통 인자는 상태로 관리되겠지.

 

이때 queryKey에 그 인자를 추가해주는 것 만으로도 새 데이터를 fetch할 수 있어.

'상태(인자)가 변경되면, effect로 fetch를 해주세요.' 라는 코드를 작성할 필요가 없다는거야.

queryKey만 변경해주면 된다는거지!

 

queryFn에 전달하는 인자를 queryKey에 포함하지 않았던 적이 없는 것 같아.

거의 모든 상황에 함수의 인자를 queryKey에도 포함했어.


하드로딩이 보고싶지 않다면, initialData

간단한 투두 앱을 예시로 탭 컴포넌트가 있다고 가정할게.

탭 전환마다 [모든 일정] / [완료 일정]을 각각 fetch 한다고 하면, 첫 탭 전환시에는(query 캐시가 없을 때) 하드 로딩 상태가 돼.

즉, 스피너가 표시될 수 있다는 거지.

 

스피너보다 더 앞서나가서 로딩 상태를 아예 안 보여주고 싶다면, initialData 옵션을 사용할 수 있어.

 

export const useTodosQuery = (state: State) =>
  useQuery({
    queryKey: ['todos', state],
    queryFn: () => fetchTodos(state),
    initialData: () => {
      const allTodos = queryClient.getQueryData<Todos>([
        'todos',
        'all',
      ])
      const filteredData =
        allTodos?.filter((todo) => todo.state === state) ?? []

      return filteredData.length > 0 ? filteredData : undefined
    },
  })

 

queryClient로 부터 [모든 일정] Query를 가져오고 클라이언트에서 필터링 한 결과를 미리 보여주는거야.

[완료 일정] fetch가 백그라운드에서 완료되면 리스트는 업데이트 되겠지.

적절한 상황에 사용해 보도록 해.


서버 상태와 클라이언트 상태를 나눠라!

useQuery로 가져온 서버 데이터를 로컬 state에 넣지 마.

React Query가 수행하는 모든 백그라운드 업데이트를 암시적으로 무시할거야.

 

그런데 만약 데이터의 기본값을 가져온 후에 랜더링 하려는 것 같이 명확한 목적을 가지고 있다면 사용해도 좋아.

대신 이 경우에는 첫 1회 fetch 외에 백그라운드 업데이트가 일어날 필요는 없겠지? 

이럴 땐 staleTime을 Infinity로 설정해서 캐시를 무한대로 설정해서 불필요한 업데이트를 막아보자.


enabled 옵션으로 많은 것을 할 수 있다.

1. 종속 쿼리

종속 쿼리는 A api에서 가져온 결과값으로 B api를 보내야 하는 경우를 말해.

옵션으로 A query의 결과값을 성공적으로 가져왔을 때에만 B query를 실행하도록 할 수 있어.
(다만 네트워크 워터폴이 발생하니, 가능하면 병렬적으로 가져올 수 있도록 api를 구성하는게 좋아. 항상 가능한건 아니겠지만...)

 

2. 쿼리 on/off

refetchInterval 옵션으로 데이터 폴링을 하고있는 경우라고 가정해봐.

만약 모달이 열려있다면, 백드롭 뒤쪽 화면은 업데이트 되지 않는게 자연스러울거야.
이런 경우에도 쿼리를 중단할 수 있어.

 

3. 사용자 입력을 기다렸다 실행하기

쿼리키에 필터 옵션들을 포함시키면서도, 유저가 필터기능을 사용하지 않는 동안에는 쿼리를 비활성화 할 수 있어.

const Test = () => {
  const [filterOption, setFilterOption] = useState(null)
	
  const { data } = useQuery({
    queryKey: ['', filterOption],
    queryFn: () => {...},
    enabled: filterOption !== null
  })

  return <div>{data}</div>
}

 

4. 사용자 입력 후엔 쿼리 비활성화 하기 

위 예제와 비슷해. 서버 데이터 보다 우선시 해서 보여줘야하는 초기 데이터가 있을 때 유용해.


query Cache를 로컬 상태로 사용하지 말것.

쿼리 캐시(queryClient.setQeuryData)는 낙관적 업데이트를 해야 하거나,

mutation후에 서버에게 받은 데이터로 덮어 써야할 때에만 사용해야해.

 

기억해! 모든 백그라운드 refetch는 기존 데이터를 덮어 쓸 수 있어. 


커스텀훅을 만들어라.

이미 익숙하겠지만, 단지 useQuery 하나를 래핑하는 용도라도 커스텀 훅은 의미가 있어.

 

실제 데이터 패칭 코드는 ui 밖으로 빼면서, useQuery 호출에 대해서는 co-location을 유지할 수 있지.

쿼리키도 하나의 파일에서 관리할 수 있고 말이야.

옵션을 변경해야한다거나, 데이터의 형태를 변경해야할 때에도 한 곳에서 해결할 수 있어. 


 

여기까지 도도형의 실전압축 reactQuery 포스팅 1편을 읽고 정리해봤다. 

역시나 아주 달달한 정보가 많다.

평소에 몰랐던 부분, 잘못 사용하고 있던 부분을 알 수 있었다.

 

staleTime 0 이라고 리랜더링 마다 쿼리쏘는게 아니라는 것, 쿼리키를 디펜던시 어레이로 바라보는 시선,

initialData, enabled 옵션의 올바른 사용예시까지...

 

아주 좋구만. 음 좋아.

 

아직도 역시나 기본이 중요하구만.

 

'Frontend > TanStack Query' 카테고리의 다른 글

Tanstack Query, TkDodo 도아일체  (0) 2023.12.15