Frontend Study

React # 11 _ React에서 Custom Hook을 활용한 데이터 Fetching과 최적화

jimmmy_jin 2025. 1. 2. 12:16

React에서 Custom Hook을 활용한 데이터 Fetching과 최적화

React 프로젝트를 진행하다 보면 데이터 Fetching과 관련된 코드를 여러 컴포넌트에서 반복적으로 작성하게 된다. 이런 반복을 줄이고 코드의 재사용성을 높이기 위해 Custom Hook을 활용할 수 있다. 이번 글에서는 데이터를 Fetching하기 위한 Custom Hook을 만들고 사용하는 과정을 정리해 보았다.


1. 기존 데이터 Fetching 코드

기존의 Fetching 로직은 아래와 같은 형태로 작성된다.

const [availablePlaces, setAvailablePlaces] = useState([]); // 데이터 상태
const [isLoading, setIsLoading] = useState(false); // 로딩 상태
const [error, setError] = useState(null); // 에러 상태

useEffect(() => {
  async function fetchPlaces() {
    setIsLoading(true);

    try {
      const response = await fetch('http://localhost:3000/places'); // API 호출
      const resData = await response.json();

      if (!response.ok) {
        throw new Error(resData.message || 'Could not fetch places.');
      }

      setAvailablePlaces(resData.places); // 상태 업데이트
    } catch (error) {
      setError({ message: error.message || 'Failed to fetch places' }); // 에러 처리
    }

    setIsLoading(false); // 로딩 종료
  }

  fetchPlaces();
}, []);

이 코드는 데이터를 가져오는 데 필요한 상태 관리(useState), 로딩 처리, 에러 핸들링 등 필수 요소를 포함하고 있지만, 여러 컴포넌트에서 반복적으로 작성해야 한다는 단점이 있다.


2. Custom Hook 작성

반복적인 Fetching 로직을 Custom Hook으로 추출하여 코드 재사용성을 높일 수 있다.

import { useEffect, useState } from 'react';

export function useFetch(fetchFn, initialValue) {
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);
  const [fetchedData, setFetchedData] = useState(initialValue);

  useEffect(() => {
    async function fetchData() {
      setIsLoading(true);
      try {
        const data = await fetchFn(); // fetchFn 실행
        setFetchedData(data); // 데이터 상태 업데이트
      } catch (error) {
        setError({ message: error.message || 'Failed to fetch data' }); // 에러 처리
      }
      setIsLoading(false); // 로딩 종료
    }
    fetchData();
  }, [fetchFn]);

  return {
    isLoading,
    error,
    fetchedData,
  };
}

3. Custom Hook의 주요 포인트

  1. Hook 이름 규칙:
    React에서 Custom Hook은 이름이 반드시 use로 시작해야 한다. 예를 들어, useFetch라는 이름은 React가 이 함수를 Hook으로 인식하게 한다. 단, React의 기본 Hook 이름(useState, useEffect)과 겹치지 않도록 주의해야 한다.
  2. 매개변수 활용:
    • fetchFn: 데이터를 가져오는 비동기 함수로, API 호출 로직을 포함한다.
    • initialValue: 초기 상태 값을 설정한다.
  3. 상태 관리:
    • isLoading: 로딩 상태를 나타낸다.
    • error: 발생한 에러를 저장한다.
    • fetchedData: Fetching된 데이터를 저장한다.
  4. 결과 반환:
    return을 통해 필요한 상태와 데이터를 반환하면, Hook을 사용하는 컴포넌트에서 쉽게 활용할 수 있다.

4. Custom Hook 사용 예시

Custom Hook을 사용하여 데이터를 Fetching하는 컴포넌트를 작성해 보자.

const {
  isLoading,
  error,
  fetchedData: availablePlaces,
} = useFetch(fetchSortedPlaces, []);
  • Object Destructuring: fetchedData를 availablePlaces라는 이름으로 변경.
  • Hook에서 반환된 상태를 활용하여 로딩 중, 에러 발생, 데이터 출력 상태를 간단히 구현.

5. 정렬된 데이터 Fetching 함수

Fetching된 데이터를 정렬하기 위해 추가 로직을 포함한 비동기 함수(fetchSortedPlaces)를 작성할 수 있다.

async function fetchSortedPlaces() {
  const places = await fetchAvailablePlaces();

  return new Promise((resolve) => {
    navigator.geolocation.getCurrentPosition((position) => {
      const sortedPlaces = sortPlacesByDistance(
        places,
        position.coords.latitude,
        position.coords.longitude
      );
      resolve(sortedPlaces);
    });
  });
}

Promise를 사용한 이유

  • navigator.geolocation.getCurrentPosition은 비동기 작업을 콜백으로 처리한다.
  • async/await는 Promise 기반으로 동작하기 때문에, 콜백을 Promise로 감싸야 await로 사용할 수 있다.

async/await를 사용한 이유

  • 코드의 가독성을 높이고, 콜백 지옥(Callback Hell)을 피하기 위해 사용.
  • 비동기 흐름을 동기 코드처럼 작성할 수 있어 직관적이다.

6. 최종 형태

이제 fetchSortedPlaces를 useFetch의 fetchFn으로 전달하여 정렬된 데이터를 Fetching할 수 있다.

const {
  isLoading,
  error,
  fetchedData: sortedPlaces,
} = useFetch(fetchSortedPlaces, []);

isLoading과 error를 활용하여 로딩 화면과 에러 메시지를 표시하고, sortedPlaces로 데이터를 화면에 출력하면 된다.


7. 마무리

Custom Hook을 사용하면 반복적인 Fetching 로직을 줄이고 코드의 가독성과 재사용성을 높일 수 있다. 또한, async/await와 Promise를 함께 사용하여 비동기 작업을 더 효율적으로 처리할 수 있다. React 개발에서 Custom Hook은 코드 구조를 간결하게 만드는 중요한 도구이므로 적극적으로 활용해 보자! 😊