✍️ What I Learned/TIL

[TIL] React Query 세팅, isLoading/isError, staleTime vs. cacheTime

Jiwon() 2023. 7. 17. 23:56

 

1. React Query란

1) Client State vs. Server State

  • Client State : 웹 브라우저 세션과 관련된 정보
    • ex. 유저의 언어, 테마 등
    • 서버에서 일어나는 일과는 아무 관련이 없는 state들
    • 단순히 사용자의 상태를 tracking하는 것
  • Server State : 서버에 저장,그러나 클라이언트에게 표시하는데 필요한 데이터들
    • ex. 블로그 포스트 data from DB

 

2) React Query의 역할

  • 서버 데이터 캐시를 관리한다.
    • React 코드에 서버데이터가 필요할 때 fetch나 axios를 사용해 서버로 바로 이동하지 않고, React Query 캐시를 요청
  • 즉, React Query 클라이언트를 어떻게 구성했느냐에 따라 해당 캐시의 데이터를 유지 관리 하는 것
  • 서버로부터 새로운 데이터를 언제 캐시에 업데이트해야하는지 지시함
    • 명령형(imperatively) : 데이터 무효화
    • 선언형(declaratively) : 어떻게 (ex. window가 focus이 됐을 때) 및 언제 re-fetch를 트리거할지 설정
  • 서버 상태 관리에 도움이 되는 많은 도구 제공
    • Loading / Error states
    • Pagination(페이지 매김)과 무한 스크롤이 필요한 경우 데이터를 조각으로 가져올 수 있는 도구도 제공
    • Prefetch : 데이터를 미리 fetch해서 캐시에 넣으면, 사용자에게 데이터가 필요할 때 앱이 캐시에서 해당 데이터를 알아서 가져옴 → 사용자는 서버에 연결할 때까지 기다릴 필요가 없게 됨
    • Mutations : 데이터의 변이(mutation)나 업데이트를 관리
    • 중복요청 제거
      • Query는 key로 식별되기 때문에 React Query는 요청을 관리할 수 있음
      • 페이지를 로드하고 해당 페이지의 여러 구성요소가 동일한 데이터를 요청하는 경우, 중복 요청을 제거하여 쿼리를 한 번에 보내도록 처리
    • Retry on error : 서버에서 오류가 발생하는 경우에 대한 retry 관리
    • Callbacks : 쿼리가 성공하거나 오류 났을 때를 구별하여 조치하도록 callback을 전달

 

 


2. Getting Started

1) 기본 세팅 및 개념

yarn add react-query
// App.jsx

import React from "react";
import Router from "./shared/Router";
import { QueryClient, QueryClientProvider } from "react-query";

const queryClient = new QueryClient();

const App = () => {
  return (
    <QueryClientProvider client={queryClient}>
      <Router />
    </QueryClientProvider>
    );
};

export default App;

 

(1) 최상위 컴포넌트에서 할 일 (index.js 또는 App.jsx)

  • Query client 생성
    • const queryClient = new QueryClient();
    • 쿼리와 서버의 데이터 캐시를 관리하는 클라이언트
    • 클라이언트가 있어야 provider를 추가할 수 있음
    • 그러면 provider가 client를 prop으로 사용하게 되면서 클라이언트가 갖고 있는 캐시와 모든 기본 옵션을 provider의 자녀 컴포넌트들(=Router 이하 모든 컴포넌트들)도 사용할 수 있게 됨
  • QueryProvider로 래핑
    • 자녀 컴포넌트에 캐시와 클라이언트 구성을 적용
    • 이에 대한 값으로 사용 될 쿼리 클라이언트 필요

 

(2) 데이터를 불러올 하위 컴포넌트에서 할 일

  • useQuery
    • 서버에서 데이터를 fetch할 때 사용하는 hook

 

 


3. isLoading과 isError 처리하기

1) isLoading

import { useState } from "react";
import { useQuery } from "react-query";

import { PostDetail } from "./PostDetail";

async function fetchPosts() {
  const response = await fetch(
    "https://jsonplaceholder.typicode.com/posts?_limit=10&_page=0"
  );
  return response.json();
}

export function Posts() {
  const [currentPage, setCurrentPage] = useState(0);
  const [selectedPost, setSelectedPost] = useState(null);

  // replace with useQuery
  // const data = [하드코딩하는 배열 형태는 naver...XXX];
  const { data, isError, isLoading } = useQuery("posts", fetchPosts);
  if (isLoading) return <h3>Loading...</h3>;

  return (
    <>
      <ul>
        {data.map((post) => (
         ...
    </>
  );
}

개발자 도구 - Network 탭에서 속도 조절하여 isLoading 화면 체크를 좀 더 쉽게 할 수 있음!

(1) isLoading vs. isFetching

  • isFetching
    • 비동기 쿼리 함수가 아직 해결되지 않았음을 의미
    • 아직 fetching을 완료하지 않았다는 의미
    • 쿼리가 Axios 호출 또는 GraphQL 호출일 수도 있음
  • isLoading
    • 아직 데이터를 가져오는 중이면 표시할 캐시 데이터조차 없다는 뜻
    • isFetching의 하위 집합
    • 쿼리 함수가 아직 해결되지 않은 것 + 캐시 된 데이터도 없음
    • 이 쿼리를 만든 적이 없다는 뜻
  • 나중에 Pagination을 진행할 때 캐시 데이터가 있을 때와 없을 때를 구분해야함!
💡 국밥으로 비유..!?

간단하게 생각하자면 isLoading은 어떤 데이터를 처음 가져올 때 사용하면 되고,
isFetching은 데이터를 다시 가져와야 할 때 사용하면 된다.

그 두개가 달라야 하나? 싶을텐데, 다시 한번 국밥을 생각해보자.
국밥을 처음 가져올때는 숟가락, 젓가락, 물도 가져다줘야 하지만, (isLoading)
다시 한번 주문하면 국밥만 가져다 준다. (isFetching)

이처럼 두 경우에 있어서 작업의 차이가 필요할 때 사용 한다고 보면 되겠다.

출처 : https://velog.io/@himprover/React-query-에서-isLoading이랑-isFetching은-뭐가-다르지

 

 

2) isError

import { useState } from "react";
import { useQuery } from "react-query";

import { PostDetail } from "./PostDetail";

async function fetchPosts() {
  const response = await fetch(
    "https://jsonplaceholder.typicode.com/posts?_limit=10&_page=0"
  );
  return response.json();
}

export function Posts() {
  ...

  // replace with useQuery
	const { data, isError, error, isLoading } = useQuery("posts", fetchPosts);
  if (isLoading) return <h3>Loading...</h3>;
  if (isError) return (
    <>
      <h3>Oops, something went</h3>
      <p>{error.toString()}</p>
    </>
  )

  return (
    <>
      <ul>
        {data.map((post) => (
         ...
    </>
  );
}

 

 


4. staleTime vs. cacheTime

1) Stale Data

  • 오래된 식빵🍞과 비슷
  • data refetching은 만료된 데이터에서만 trigger된다
  • 데이터 리페칭 실행에는 만료된 데이터 외에도 여러 트리거가 있음
    • ex. 컴포넌트 리마운트, 윈도우 refocus
    • staleTime은 데이터를 허용하는 최대 나이라고 생각하기
    • 데이터가 만료됐다고 판단하기 전까지 허용하는 시간을 뜻함
  • 설정 방법
// Posts.jsx
// 쿼리 데이터를 사용하는 앱에서 useQuery의 세 번째 인자에 설정해주면 된다

...

const { data, isError, error, isLoading } = useQuery(
	"posts",
	fetchPosts,
	{ staleTime: 2000 },
);

 

❓ 왜 stale time의 기본 설정값은 0일까?

react query 개발자 says..
- ’업데이트가 왜 안되죠?’ 보다 ‘데이터를 어떻게 늘 최신 상태로 유지하나요?’가 훨씬 나은 질문이다.
- staletime이 0이면 데이터는 항상 만료상태이므로 서버에서 다시 가져와야 한다고 가정하게 됨
 → 그러면 실수로 클라이언트에게 만료된 데이터를 제공할 가능성이 훨씬 줄어듦!

 

 

2) cacheTime과 staleTime

  • staleTime은 re-fetching 할 때의 고려사항이다.
  • cache는 나중에 다시 필요할 수도 있는 데이터를 위한 것
    • 쿼리에 대한 활성 useQuery가 없는 경우 해당 쿼리 데이터는 cold storage로 이동
    • 설정한 cacheTime(default값은 5분)이 지나면 캐시 데이터는 만료됨
    • cacheTime이 관찰하는 시간의 양은 쿼리에 대한 useQuery가 활성화된 후 경과한 시간 → 페이지에 표시되는 컴포넌트가 특정 쿼리에 대해 useQuery를 사용한 시간
    • cache가 만료되면 만료된 cache data는 가비지 컬렉션이 실행되고, 클라이언트는 데이터를 더이상 사용할 수 없게 됨
  • cache는 화면이 fetching 중일 때, 화면에 보여지는 backup data(최신 데이터X)의 역할
    • 데이터 fetching을 중단하지 않으므로 서버의 최신 데이터로 새로고침 가능
    • 하지만 fetching 중일 때 계속해서 아무 데이터도 없는 빈 데이터만 보는 경우가 생길 수 있음
    • 새로운 데이터를 fetching하는 동안 약간 오래된 데이터(=캐시 데이터)를 보는 게 더 나은 선택
    • 예외) 만료된 데이터가 위험할 수 있는 앱의 경우 cacheTime을 0으로 설정하면 된다.