Next.js에서 React-Query 사용하기

김재한·2023년 6월 16일
0

React 기초

목록 보기
6/9

1. 서론

1) React-Query란?

  • 어플리케이션의 비동기 데이터를 쉽게 관리할 수 있으며 캐시 및 동기화 기능을 제공하는 React용 라이브러리
  • 실시간 데이터 패칭, 페이지네이션, 오류 헨들링 등 유용한 기능 제공

2) 장점

  • 서버의 상태를 불러오고 캐싱하며, 지속적으로 동기화하고 업데이트해 무결성을 보장한다.
  • 복잡하고 장황한 코드가 필요한 다른 라이브러리(Redux, MobX 등)와는 달리 ReactComponent 내부에서 간단하고 직관적으로 API를 사용할 수 있다.
  • 캐싱, 다양한 옵션, 에러핸들링 기능을 제공해 API요청과 관련된 번잡한 작업 없이 '핵심로직'에 집중할 수 있다.
  • 페이지네이션 & 무한 스크롤을 지원한다.
  • React Hook과 비슷한 구조로 사용하기 쉽다

2.시작하기

1) 설치

npm install @tanstack/react-query

2) queryClient 생성 ( _app.js )

export default function MyApp({ Component, pageProps }) {

  // 순수 react 의 경우에는 이렇게 선언
  // const queryClient = new QueryClient();
  
  const [queryClient] = useState(()=> new QueryClient({
    // 옵션 추가 가능
    defaultOptions:{
      queries:{
        retry:1,
        retryDelay: 0,
        staleTime: 60 * 1000
      }
    }
  }));

  return (
    <QueryClientProvider client={queryClient}>
  		<Hydrate state={pageProps.dehydratedState}>
	      <Component {...pageProps} />
		</Hydrate>
    </QueryClientProvider>

  )
}

[ queryClient를 useState로 선언하는 이유 ]
Next.js의 경우 페이지를 이동할 때 _app.js부터 새롭게 렌더링 시키기 때문에 useState를 이용해 한번만 선언되게 만들어준다. 이렇게 하지 않으면 새로운 queryClient가 생성되면서 기존 데이터가 유실될 수 있다.

QueryClientProvider는 ContextProvider로 동작하며 하위 컴포넌트에서 QueryClient를 사용할 수 있게 해준다. ( 캐시 데이터 사용 가능)

[ Prefetching data with React Query ]
서버사이드에서 useQuery를 사용해 데이터를 prefetching 하는 방법에는 두 가지가 있다.

  1. initialData 속성 사용
  2. Hydration 사용

[ InitialData 사용 ]

  1. getStaticProps 또는 getServerSideProps에서 데이터를 받아온 후 props로 전달한다.
  2. 클라이언트는 전달받은 props를 useQuery 훅의 initialData 옵션에 할당한다.
export async function getStaticProps() {
  const posts = await getPosts()
  return { props: { posts } }
}

function Posts(props) {
  const { data } = useQuery(['posts'], getPosts, { initialData: props.posts })

  // ...
}

[ Hydration 사용 ]

서버사이드에서 prefetch한 쿼리를 queryClient에 dehydrate 하는 방식이다. (SSG, SSR 동일)

  1. 매 요청마다 새로운 queryClient를 생성한다.
  2. 서버사이드에서 prefetchQuery를 통해 데이터를 prefetch 한다.
  3. dehydrate를 통해 해당 쿼리를 캐싱하고 prop으로 페이지에 넘겨준다.
export async function getStaticProps() {
  const queryClient = new QueryClient()

  await queryClient.prefetchQuery(['posts'], getPosts)

  return {
    props: {
      dehydratedState: dehydrate(queryClient),
    },
  }
}

function Posts() {
  // This useQuery could just as well happen in some deeper child to
  // the "Posts"-page, data will be available immediately either way
  const { data } = useQuery(['posts'], getPosts)

  // This query was not prefetched on the server and will not start
  // fetching until on the client, both patterns are fine to mix
  const { data: otherData } = useQuery(['posts-2'], getPosts)

  // ...
}

3. 사용하기

1) useQuery

  • get API요청을 수행하기 위한 hook이다.
    - post, update, delete 등은 useMutation을 사용
  • 기본적으로 비동기로 동작하며 useQueries를 통해 여러 쿼리를 수행할 수 있다.
    - enabled 옵션을 사용하면 동기적으로 사용 가능

파라미터

  • queryKey: 해당 쿼리의 고유 식별자
  • queryFn: 쿼리에 사용할 promise 기반의 비동기 API 함수
  • options: 쿼리에 사용할 옵션
    -staleTime: 데이터가 stale 상태가 되지 않도록 fresh하게 유지하는 시간 ( 기본값: 0 )
    - cacheTime: 데이터가 캐싱되어 유지되는 시간. 시간이 지나면 자동 삭제 ( 기본값: 5분 )
    - refetchOnMount: 화면이 마운트 될 때마다 stale 상태의 데이터 refetch ( 기본값: true)
    - refetchOnWindowFocus: 브라우저에 포커스가 들어왔을 때 stale 상태의 데이터 refetch
    - refetchOnReconnect: 네트워크가 다시 연결되었을 때 stale 상태의 데이터 refetch
    - initialData: 초기 데이터 설정
    - keepPreviousData: 이전의 queryKey 데이터 유지
    더 많은 옵션 값은 TanStack 에서 확인 가능

리턴값

  • data: fetch한 데이터. 데이터가 fetch되기 전까지는 undefined의 값을 갖는다.
  • status: 쿼리의 상태 ( loading, error, success )
  • error: 발생한 오류. 정상처리 될 경우 undefined를 갖는다.
  • isFetching: 쿼리가 fetching 중인지 여부를 나타내는 값으로 Boolean 값을 갖는다.

[ 예시 ]

// memberApi.js
export const getMemberList = (param) => {
  return useQuery(["memberLists", param], ()=>getMemberListFetch(param),{
    ...queryOption,
    onSuccess: data => {
      // 성공 시 호출
    },
    onError: error => {
      // 실패 시 호출   
    }
  })
}

export const getMemberListFetch = async (param) => {
  const data = await request.get(PATH_ADMIN_API.MEMBER.LIST(param)).catch((error) => {
    throw error
  })
  return data
}
// member.js
export default function MemberInfoTable() {
	const {data:memberInfoLists} = getMemberList(param)
	return ...
}

2) useQuery 동기적 실행

  • enabled 옵션을 사용해 특정 쿼리를 동기적으로 실행시킨다.

[ 예시 ]

const { data: todoList, error, isFetching } = useQuery("todos", fetchTodoList);
const { data: nextTodo, error, isFetching } = useQuery(
  "nextTodos",
  fetchNextTodoList,
  {
    enabled: !!todoList // true가 되면 fetchNextTodoList를 실행한다
  }
);

3) QueryKeys

  • reactQuery에서 캐싱을 관리하기 위한 unique Key이다
  • uniqueKey를 배열로 넣으면 query함수 내부에서 변수로 사용이 가능하다.
const info = {
  memberId: "2"
};

const result = useQueries([
  {
    queryKey: ["memberInfo", info.memberId],
    queryFn: params => {
      console.log(params); // {queryKey: ['memberInfo', '2'], pageParam: undefined, meta: undefined}

      return api.getRunInfo(riot.version);
    }
  },
  {
    queryKey: ["getSpell", riot.version],
    queryFn: () => api.getSpellInfo(riot.version)
  }
]);

4) QueryCache

  • onError, onSuccess 콜백을 사용해 애플리케이션 전역에서 이벤트 핸들링
  • queryCache를 사용해 custom errorProvider를 만들어 관리중이다.

[ Method ]

  • find: 캐시에서 기존 쿼리 인스턴스를 가져옴 ( 없으면 undefined )
const queryCache = queryClient.getQueryCache();

const query = queryCache.find({queryKey})
  • findAll: 입력한 queryKey가 부분적으로 일치하는 모든 인스턴스를 가져옴
const query = queryCache.findAll({queryKey})
  • subscribe: 쿼리 캐시를 구독하는 기능으로 쿼리의 상태 변경을 감지한다.( 통신 성공, 실패 등 )
const queryCache = queryClient.getQueryCache();

const callback = event =>{
  // 에러처리 함수
  console.log('error: ', event?.query?.state?.error)
}
const unsubscribe = queryCache.subscribe(callback));

callback 함수는 쿼리 캐시가 업데이트 될 때마다 호출된다. 이 기능을 이용해 에러를 캐치해 처리하는 errorProvider를 만들어 전역으로 관리했다.

5) useMutation

  • create, update, delete 할 때 사용한다.

파라미터

  • mutationKey: mutation에 사용할 unique Key 값
  • mutationFn: promise 기반의 비동기 API함수
  • options: mutation에 사용할 옵션

[ 예시 ]

//memberApi.js
export const putMemberAuthChangeMutate = (userKey) => {
  return useMutation((param) => putMemberAuthChangeFetch(param, userKey))
}

export const putMemberAuthChangeFetch = async (param, userKey) => {
  return await request.put(PATH_ADMIN_API.MEMBER.AUTH_CHANGE(userKey),param).catch((error) => {
    throw error
  })
}
//member.js
const {mutate: changeAuth} = putMemberAuthChangeMutate(userKey);

const handleChangeAuth = () =>{
    changeAuth(param,{
      onSuccess: () => {
       //성공 시 해당 queryKey의 api를 재실행
        queryClient.invalidateQueries('memberLists')
        queryClient.invalidateQueries('memberDetail')
      }
    })
  }

참고

0개의 댓글