본문 바로가기
프레임워크&라이브러리/React-Query

React-Query 입문 [필요성, query, mutation]

by whale in milktea 2023. 7. 8.

공식문서 : https://tanstack.com/query/latest/docs/react/quick-start

참고자료 : https://tech.kakaopay.com/post/react-query-1/

필요성

프로젝트를 진행하면서 apis 폴더에 모든 서버 통신 함수들을 관리하다보니, 같은 동작임에도 불필요하게 반복되는 부분이 많았다.

axios는 서버 통신을 할 때, fetch에 비해 json.stringfy()와 같은 필수적이지만 중복되는 동작들을 처리해주고 error, data, loading에 따른 동작을 비동기적으로 처리해주는 강력한 도구이다.

 

그러나 캐싱과 같은 서버 상태를 다루려면 종전의 fetch와 마찬가지로 필수적이지만 중복되는 코드들이 많아진다.

이 문제는 사실 redux와 같은 전역 상태 라이브러리를 같이 활용하여 데이터를 받아오고, 전역으로 관리되는 상태에 적용하여 React 컴포넌트 자체를 렌더링시키면 서버 통신이 이뤄질 때 리렌더링 되므로 어느정도 해결할 수 있다고 하지만,

 

이렇게 되면 너무나 잦은 리렌더링이 발생하고 이를 제어하고자 한다면 또 다른 조건문을 활용하여 다양한 컴포넌트들을 제어해야 하기 때문에 서버 상태에 따른 컴포넌트 관리가 더욱 복잡해지게 된다.

 

React-Query는 axios와 같은 비동기 함수들을 서버 통신 함수로 사용하면서, 캐싱과 같은 서버 상태에 따른 제어와 예외 처리를 효과적으로 지원하는 라이브러리이다. 이러한 이유로 React-query를 도입하여 프로젝트를 리팩토링해보고, 이미 Custom-hook처럼 사용되고 있는 React-query의 문법을 활용하여 중복되는 코드들을 제어해보고자 한다.

 

React-Query의 3가지 핵심 컨셉

This code snippet very briefly illustrates the 3 core concepts of React Query:
Queries : 받다
Mutations : 보내다
Query Invalidation : 캐시 무효화
// Query : 데이터 get 요청
// 공식문서 예제
function Todos() {
  const { isLoading, isError, data, error } = useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodoList,
  })

  if (isLoading) {
    return <span>Loading...</span>
  }

  if (isError) {
    return <span>Error: {error.message}</span>
  }

  // We can assume by this point that `isSuccess === true`
  return (
    <ul>
      {data.map((todo) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  )
}
// Mutation : 데이터 수정 요청
function App() {
  const mutation = useMutation({
    mutationFn: (newTodo) => {
      return axios.post('/todos', newTodo)
    },
  })

  return (
    <div>
      {mutation.isLoading ? (
        'Adding todo...'
      ) : (
        <>
          {mutation.isError ? (
            <div>An error occurred: {mutation.error.message}</div>
          ) : null}

          {mutation.isSuccess ? <div>Todo added!</div> : null}

          <button
            onClick={() => {
              mutation.mutate({ id: new Date(), title: 'Do Laundry' })
            }}
          >
            Create Todo
          </button>
        </>
      )}
    </div>
  )
}

get 요청 예시

역시 처음 서버 통신 연습으로 가장 만만한 것은 get 요청이다.

데이터는 OpenWeatherApi를 사용했으며, 데이터 로딩 / 에러 / fetch 성공 => 데이터 모두 console로 확인하고자 했다.

// src/apis/Openweather.ts

import axios from 'axios';
import { useQuery } from '@tanstack/react-query';

const weatherRequestApi = import.meta.env.VITE_REACT_APP_OPENWEATHER;

export const getWeather = () => {
  // eslint-disable-next-line react-hooks/rules-of-hooks
  const { data, isLoading, error } = useQuery(['weather'], () => {
    return axios.get(weatherRequestApi)
  })
  if (isLoading) console.log('Loading...')
  if (error) console.log('Error')
  return data
}
// src/components/Reactquery.tsx

import { getWeather } from "../apis/openweather";

const ReactQueryPractice = () => {
  const handleClick = () => {
    const data = getWeather();
    console.log(data);
  };

  return (
    <>
      <button onClick={handleClick}>리액트 쿼리 연습</button>
    </>
  );
};

export default ReactQueryPractice;

처음에는 App.tsx에 위의 컴포넌트를 연결해서 data를 확인하려고 했는데.. 동작을 안했다.

왜 문제일까 생각하다가 문득 "useQuery"라는 단어에 꽃혀서 찾아보니,,,

그렇다.. 모든 React-Hook은 컴포넌트의 생명주기에 따라 생성/소멸되는 함수이다.

 

그래서 다음과 같이 코드를 수정했다.

+ 이왕 수정하는 김에 Loading 상태를 활용해서 UX와 데이터 병목 현상 방지를 미리 연습할 겸 setTimeout 함수도 추가했다.

// src/apis/Openweather.ts

import axios from 'axios';

const weatherRequestApi = import.meta.env.VITE_REACT_APP_OPENWEATHER;

export const getWeather = () => {
  setTimeout(() => {
  return axios.get(weatherRequestApi);
  }, 1000);
}
// src/components/Reactquery.tsx

import { useQuery } from "@tanstack/react-query";
import { getWeather } from "../apis/openweather";

const ReactQueryPractice = () => {
  const { data, isLoading, isError, error } = useQuery(["weather"], getWeather);

  const handleClick = () => {
    if (isError) console.log(error);
    if (isLoading) console.log("Loading...");
    if (data) console.log(data);
    
  };

  return (
    <>
      <button onClick={handleClick}>리액트 쿼리 연습</button>
    </>
  );
};

export default ReactQueryPractice;

위의 코드를 실행시켜보니 이제 "동작은 한다"

그러나 2가지 에러 메세지가 뜨고 있다.

 

첫번째는 받아온 data의 타입이 never로 표시되어 있다. 이는 바닥 타입이라, 더 이상의 조작이 불가능하다.

const data: never

두번째는 콘솔 창에 아래의 에러 메세지가 뜬다

Error: ["weather"] data is undefined

역시.. Query-Key 개념 타입스크립트를 신경쓰지 않고 일단 작성해보면서 학습해나가자고 했는데.. 그 부분에서 바로 에러가 떴다.

이후의 내용은, 다음 포스팅에서..!