xction-dev / xction.co.kr

Xction!의 홈페이지를 만들고 있습니다
0 stars 0 forks source link

[Tutorial] useQuery의 원리와 사용 방법 #19

Open designDefined opened 11 months ago

designDefined commented 11 months ago

useQuery

개괄

React-Query, 또는 Tanstack-Query(최신 버전의 이름)는 서버에 값을 요청하고 가져오는 fetch 동작을 깔끔하고 선언적으로 수행하기 위해 이용되는 라이브러리이다. 그 중 useQuery는 GET을 할 때 사용하는 훅으로, 서버에서 GET 요청을 보내 가져온 값을 저장하고 새로고침하는 역할을 한다. (POST나 PUT 등의 요청은 useMutation을 사용한다). 최신 버전의 공식 문서

예시 (@nuagenic 코드에서 발췌)

const { data, error, status } = useQuery({
  queryKey: ["project", id],
  queryFn: () =>
    fetch(`/api/mock/project/${id}`).then((res) =>
      res.ok ? res.json() : Promise.reject(res),
    ),
  staleTime: 1000 * 60,
  gcTime: 1000 * 60 * 60,
  retry: 0,
});

queryFn

useQuery 훅의 동작은 다음과 같다.

  1. queryFn 함수를 자동으로 실행시켜 값을 가져온다
  2. 가져온 값을 data에 저장하고, 필요시 queryFn을 추가적으로 실행시켜 값을 새로고침한다

리액트 쿼리의 특징은 1에서 queryFn을 개발자가 직접 콜하지 않아도, 자동으로 작동이 된다는 것이다. 즉 react-query 라이브러리의 어딘가에는 다음과 비슷한 코드 조각이 있을 것이다.

const response = await queryFn(); // 서버 데이터를 가져오니 await을 한다고 가정하자.
setData(response); // data 부분에 응답을 저장하는 가상의 함수.

주목해야할 것은, queryFn이 어떠한 인자도 없이 콜 된다는 것이다. queryFn에 특정 인자를 넣어서 작동시키려면 개발자가 직접 함수를 콜해줘야 할 것인데, 아까 1에서 보았듯 훅이 자동으로 함수를 실행시켜 값을 가져온다고 했다. 애초에 GET 요청에는 body를 보낼 수 없으니, hook을 선언하는 시점에서 한 번 등록된 함수를 계속 재사용하게 만들어도 문제가 없다고 판단한 것 같다.

여튼, queryFn이 제대로 동작하려면 위와 같은 이유에서 아무 인자를 받지 않는 함수여야 한다. 예시에도 () =>fetch("엔드포인트").then(~) 꼴의 함수를 사용한 모습을 볼 수 있다. 만약 fetch("엔드포인트").then(~)이나 (id:number)=>fetch("엔드포인트"+id).then(~)과 같은 함수를 넣는다면 제대로 동작하지 않을 것이다.

그런데 우리는 종종 GET 요청에도 인자를 넣고 싶은 기분을 느낄 때가 있다. 바로 project/${project_id}와 같은 동적 엔드포인트에 요청을 보내야 하는 경우이다. 이럴 때는 어떻게 해야할까? 기본적으로는 위의 예시처럼 ${project_id}와 같은 리터럴 문법을 이용해 엔드포인트를 다르게 해줄 수 있을 것이다. 그런데 만약 api 함수를 따로 분리하고 싶다면, 어쩔 수 없이 함수에 인자를 넣어야만 할 텐데 어떻게 하면 좋을까?

고차 함수를 이용한 코드 분리

바로 ()=>fetch() 꼴의 함수를 리턴하는 고차 함수를 이용하는 것이다. useQuery는 훅이니 반드시 컴포넌트 내에서만 사용할 수 있고, 동적인 컴포넌트가 마운트 될 때 project_id와 같은 동적 라우팅 정보는 이미 결정된다. 다시 말해 useQuery를 훅을 콜하는 순간에 project_id의 값은 반드시 정해져 있다는 것이다.

그렇다면 (id)=>()=>fetch()와 같은 모양의 고차 함수가 있다고 해보자. 이 함수(편의상 functionA로 지칭)에 id 인자를 넣어 콜하면 queryFn 필드가 요구하는 인자가 없는 함수가 리턴된다. 따라서 다음과 같이 선언된 useQuery 함수는 아무 문제 없이 작동할 것이다.

const id = props.params.id;

const {data, status} = useQuery({
  queryKey: ["some", "query", "key"],
  queryFn: functionA(id) // ()=>fetch(~~~) 꼴의 함수와 동일
});

코드를 분리한 예시

위 내용을 종합하여 예시 코드를 고쳐보면 다음과 같을 것이다.

const getProjectById = (id:string) => () => fetch(`/api/mock/project/${id}`).then((res) => res.ok ? res.json() : Promise.reject(res));

function Component () {
  const id = props.params.id; // id 가져오기

  const { data, error, status } = useQuery({
    queryKey: ["project", id],
    queryFn: getProjectById(id) // ()=>fetch(`/api/mock/project${id}`).then(~~) 과 동일
  });

  return ...(이하 생략)
}

queryKey

추후 작성 예정.

nuagenic commented 11 months ago

최고의 documentation!