main-logo

React-Query

React-Query에 대해 알아보자

profile
TY
2023년 04월 07일 · 0 분 소요

들어가며

React-Query는 React 기반의 프론트엔드 개발에서 데이터 관리를 효율적으로 처리하기 위한 도구로, 다양한 기능을 제공하며, 데이터의 캐싱, 업데이트, 오류 처리, 최적화 등을 지원하는 라이브러리이다.

서버에서 가져온 데이터를 캐싱하여 성능을 최적화하고, 데이터를 갱신하거나 삭제하는 등의 작업을 간편하게 처리할 수 있게 도와주며, 이를 통해 불필요한 네트워크 요청을 최소화하고, 애플리케이션의 데이터 흐름을 효율적으로 관리할 수 있다.

이번 글에서는 React-Query에 대해 간략하게 알아보자.

쿼리 (Queries)


React-Query는 서버에서 데이터를 가져오는데 사용되는 쿼리(Query)를 제공한다.
쿼리는 비동기 함수로, 데이터를 가져와서 자동으로 캐싱하고, 성공, 실패, 로딩 등의 다양한 상태를 관리한다.
자동으로 캐싱되므로 불필요한 중복 요청을 방지하고, 성능을 향상키시는 효과가 있다.
useQuery 훅을 사용하여 컴포넌트 내에서 호출되고, 필요한 데이터를 가져와서 컴포넌트 내에서 사용할 수 있게 한다.

import React from 'react';
import { useQuery } from 'react-query';

// 사용자 데이터를 가져오는 비동기 함수
const fetchUserData = async () => {
  const response = await fetch('/api/user'); // /api/user 엔드포인트로 데이터를 요청
  const data = await response.json(); // 응답 데이터를 JSON 형식으로 변환
  return data; // 변환된 데이터 반환
};

const UserProfile = () => {
  // useQuery 훅을 사용하여 쿼리 실행 및 결과 상태 관리
  const { data, isLoading, isError, error } = useQuery('profileData', fetchUserData);

  if (isLoading) {
    // 데이터 로딩 중일 때
    return <div>Loading...</div>; // "로딩 중..." 메시지 표시
  }

  if (isError) {
    // 데이터 로딩 중에 오류가 발생했을 때
    return <div>Error: {error.message}</div>; // 오류 메시지 표시
  }

  // 데이터 로딩이 완료되고 오류가 없을 때
  return (
    <div>
      <h1>User Profile</h1>
      <p>Name: {data.name}</p> {/* 데이터의 이름 속성 표시 */}
      <p>Email: {data.email}</p> {/* 데이터의 이메일 속성 표시 */}
    </div>
  );
};

export default UserProfile;

여기서 useQuery 부분의 첫번째 매개변수인 'profileData' 는 해당 쿼리의 고유한 key가 된다.
이 key를 이용해 데이터를 캐싱하여 같은 쿼리를 여러군데에서 사용할 때에 캐싱된 데이터를 활용하여 불필요한 요청을 최소화 하여 성능 향상에 도움이 된다.

더불어 getQueryData함수를 사용하여 캐시된 쿼리의 key를 호출하게 되면 해당 쿼리의 캐싱된 데이터 값을 가져올수 있으므로 여러모로 유용하다.

뮤테이션(Mutation)


React-Query는 useMutation이라는 훅을 제공하여 뮤테이션(데이터 업데이트)을 수행하는 기능을 제공한다.
이를 통해 서버에 데이터를 변경하고, 그에 따른 UI 업데이트를 쉽게 처리할 수 있다.

먼저, useMutation 훅을 사용하여 뮤테이션을 정의하면 된다.
첫 번째 매개변수로 뮤테이션 함수를 전달하고, 두 번째 매개변수로는 뮤테이션의 옵션을 설정하면 된다.
뮤테이션 함수는 실제로 서버와의 통신을 수행하여 그 결과를 반환해야 한다.

예를 들어, 다음과 같이 createPost라는 뮤테이션 함수를 정의할 수 있다.

import React, { useState } from 'react';
import { useMutation, useQueryClient } from 'react-query';

async function createPost(postData) {
  // 서버로 데이터를 전송하고, 생성된 포스트 데이터를 반환하는 로직
  const response = await fetch('/api/posts', {
    method: 'POST',
    body: JSON.stringify(postData),
    headers: {
      'Content-Type': 'application/json',
    },
  });

  const data = await response.json();
  return data;
}

function PostForm() {
  const queryClient = useQueryClient();

  // useMutation 훅을 사용하여 뮤테이션 실행 및 상태 관리
  const [mutate, { data, error, isLoading }] = useMutation(createPost, {
    // 뮤테이션 성공 시, 캐시 업데이트를 수행하는 콜백 함수
    onSuccess: (newPost) => {
      // 기존 캐시에서 'posts' 쿼리 데이터 가져오기
      const previousPosts = queryClient.getQueryData('posts');

      // 새로운 포스트 데이터를 기존 쿼리 데이터에 추가
      queryClient.setQueryData('posts', [...previousPosts, newPost]);
    },
  });

  const [formData, setFormData] = useState({
    title: '',
    content: '',
  });

  // 폼 입력값 변경 시, 상태 업데이트를 처리하는 이벤트 핸들러
  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData((prevData) => ({
      ...prevData,
      [name]: value,
    }));
  };

  // 폼 제출 시, 뮤테이션 실행 및 폼 데이터 초기화
  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      // 뮤테이션 실행
      await mutate(formData);
      // 뮤테이션 성공 시, 폼 데이터 초기화
      setFormData({
        title: '',
        content: '',
      });
    } catch (error) {
      // 뮤테이션 실패 시, 에러 처리 로직
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" name="title" value={formData.title} onChange={handleChange} />
      <textarea name="content" value={formData.content} onChange={handleChange} />
      {/* 폼 제출 버튼, 로딩 중일 경우 비활성화 */}
      <button type="submit" disabled={isLoading}>
        {isLoading ? '로딩 중...' : '포스트 생성'}
      </button>
    </form>
  );
}

export default PostForm;

위 예시 코드에서는 onSuccess 옵션을 사용하여 뮤테이션 성공 시에 캐시를 업데이트하는 로직을 추가하였다.

이를 통해, 새로운 포스트 데이터를 기존의 posts 쿼리 데이터에 추가하고, React-Query가 자동으로 UI를 업데이트하도록 할 수 있다.

이와 같이 useMutation 훅을 활용하여 뮤테이션 결과에 따른 UI 업데이트를 처리하면, 클라이언트 측에서 간편하게 데이터 상태를 관리하고, 서버와의 상호작용을 처리할 수 있다.

캐싱(Caching)


React-Query는 서버에서 가져온 데이터를 내부적으로 캐싱하여 다음에 같은 데이터를 요청할 때 네트워크 요청을 최소화한다.
캐시는 기본적으로 메모리에 저장되며, 필요에 따라 로컬 스토리지 등 다양한 저장소에 데이터를 저장할 수도 있다.
이를 통해 애플리케이션의 성능을 향상시키고, 서버의 부하를 줄일 수 있으며, 설정된 유효기간에 따라 자동으로 갱신이 된다.

또한, 캐싱된 데이터는 로컬에서 빠르게 접근이 가능하므로, 사용자 경험을 향상시키거나 개선할 수 있다.

import React from 'react';
import { useQueryClient, useQuery } from 'react-query';

// 포스트 데이터를 가져오는 비동기 함수
const getPosts = async () => {
  const response = await fetch('/api/posts'); // /api/posts 엔드포인트로 데이터를 요청
  const data = await response.json(); // 응답 데이터를 JSON 형식으로 변환
  return data; // 변환된 데이터 반환
};

const PostList = () => {
  const queryClient = useQueryClient();
  const { data, isLoading, isError, error } = useQuery('posts', getPosts, {
    // 캐싱 옵션 설정
    cacheTime: 300000, // 데이터 캐싱 시간 설정 (5분)
  });

  if (isLoading) {
    // 데이터 로딩 중일 때
    return <div>Loading...</div>; // "로딩 중..." 메시지 표시
  }

  if (isError) {
    // 데이터 로딩 중에 오류가 발생했을 때
    return <div>Error: {error.message}</div>; // 오류 메시지 표시
  }

  // 데이터 로딩이 완료되고 오류가 없을 때
  return (
    <div>
      <h1>Post List</h1>
      {data.map((post) => (
        <div key={post.id}>
          <h3>{post.title}</h3>
          <p>{post.body}</p>
        </div>
      ))}
    </div>
  );
};

export default PostList;

최적화(Optimization)


React-Query는 성능 최적화를 위한 다양한 기능을 제공한다.
쿼리나 뮤테이션의 동시 실행, 데이터 패칭의 배치 처리, 데이터 갱신의 인터벌 설정 등을 통해 네트워크 요청의 수를 최소화하고, 데이터의 로딩 시점과 우선순위를 조절하여 원활한 사용자 경험을 제공할 수 있다.

여기에서는 데이터 갱신 주기를 위한 staleTime을 알아보자.

import React from 'react';
import { useQuery } from 'react-query';

const fetchPosts = async () => {
  // 데이터를 가져오는 비동기 함수
  const response = await fetch('/api/posts');
  const data = await response.json();
  return data;
};

const PostsList = () => {
  // 쿼리 훅을 사용하여 데이터를 가져옴
  const { data, status, error } = useQuery('posts', fetchPosts, {
    staleTime: 30000, // 30초 동안 캐시된 데이터가 유효함
  });

  if (status === 'loading') {
    // 데이터 로딩 중일 때
    return <div>Loading...</div>; // "로딩 중..." 메시지 표시
  }

  if (status === 'error') {
    // 데이터 로딩 중에 오류가 발생했을 때
    return <div>Error: {error.message}</div>; // 오류 메시지 표시
  }

  // 데이터 로딩이 완료되고 오류가 없을 때
  return (
    <div>
      <h1>Posts List</h1>
      {data.map((post) => (
        <div key={post.id}>
          <h3>{post.title}</h3>
          <p>{post.body}</p>
        </div>
      ))}
    </div>
  );
};

export default PostsList;

staleTime을 설정하면, 쿼리 데이터는 유효 기간 동안은 캐시에서 가져오며, 이후에는 서버에 요청하여 데이터를 갱신하게 된다. 이를 통해 네트워크 요청을 최소화하고, 사용자 경험을 향상시킬 수 있다.

위의 cacheTime과 다른점이 뭔가 싶다.

cacheTime과 staleTime은 react-query에서 각각 다른 목적으로 사용되는 옵션이다.
동시에 사용할 수는 있지만, 서로 다른 동작을 수행하므로 함께 사용할 때 주의가 필요하다고 한다.

  • cacheTime: cacheTime은 데이터를 캐시에 저장할 시간을 설정하는 옵션이다. 이 시간이 지나면, 다음 요청 시에는 서버로부터 데이터를 다시 가져와서 캐시를 갱신한다. 즉, cacheTime이 지나기 전까지는 캐시된 데이터를 사용하여 UI를 업데이트하게 된다.
  • staleTime: staleTime은 캐시된 데이터가 유효한 시간을 설정하는 옵션이다. 이 시간이 지나면, 다음 요청 시에는 캐시된 데이터를 사용하여 UI를 업데이트하며, 동시에 서버로 데이터를 요청하여 새로운 데이터를 가져온다. 이렇게 하면 사용자에게는 최신 데이터를 보여주면서, 빠른 UI 업데이트를 제공할 수 있다.

cacheTime과 staleTime을 함께 사용하면, cacheTime이 지나기 전까지는 캐시된 데이터를 사용하여 UI를 업데이트하다가, staleTime이 지나면 서버로부터 새로운 데이터를 가져와서 UI를 업데이트한다.
이를 통해 최신 데이터를 보여주면서, 빠른 UI 업데이트를 제공할 수 있다.

예를 들어, cacheTime을 5분(300000ms)으로 설정하고, staleTime을 30초(30000ms)로 설정하면, 초기에는 캐시된 데이터를 사용하여 UI를 업데이트하고, 30초가 지나면 서버로부터 새로운 데이터를 가져와서 UI를 업데이트하게 된다. 이를 통해 사용자에게 최신 데이터를 빠르게 보여주면서, 불필요한 서버 요청을 줄일 수 있다.

마치며

React-Query는 데이터 캐싱과 성능 최적화를 위한 강력한 도구로서, Front-end 개발자들에게 많은 도움을 주고 있다.
이 블로그 글을 통해 React-Query의 간략한 사용 방법과 주요 기능, 최적화 방법 등에 대해 다뤄보았다.

React-Query를 사용하면, 복잡한 데이터 흐름을 간편하게 관리하고, 캐싱과 최적화를 통해 더 나은 성능을 경험할 수 있다.

이를 통해 사용자 경험을 향상시키고, 개발자들은 더 효율적으로 Front-end 애플리케이션을 개발할 수 있게 될거 같다.

참고 문서