React.js 커스텀 훅스(Custom Hooks) 만들기: 코드 재사용성 극대화

작성일 :

React.js 커스텀 훅스(Custom Hooks) 만들기: 코드 재사용성 극대화

React.js는 컴포넌트 기반의 구조 덕분에 재사용 가능한 UI 요소를 쉽게 만들 수 있는 장점이 있습니다. 하지만 컴포넌트가 복잡해질수록 상태 관리와 로직의 재사용이 어려워질 수 있습니다. 이러한 문제를 해결하기 위해, React에서는 커스텀 훅스(Custom Hooks)라는 개념을 도입했습니다. 커스텀 훅스를 사용하면 컴포넌트 간의 로직을 간단하고 효율적으로 공유할 수 있습니다. 이번 글에서는 커스텀 훅스의 개념, 작성 방법, 그리고 실전 예제를 통해 그 유용성을 알아보겠습니다.

커스텀 훅스의 개념

기본적으로 훅은 React 함수형 컴포넌트에서 상태와 생명주기 기능을 가능하게 하는 특수한 함수입니다. useState, useEffect 같은 내장 훅은 이미 익숙하실 텐데요, 커스텀 훅은 이러한 내장 훅들을 조합해 나만의 훅을 만드는 것입니다.

커스텀 훅은 use로 시작하는 이름을 가지고, 다른 훅처럼 상태와 생명주기 관리를 할 수 있습니다. 이를 통해 복잡한 로직을 재사용 가능하게 만들고, 코드를 더 깨끗하고 유지보수 가능하게 합니다.

예시: 기본 커스텀 훅

예를 들어, 사용자의 현재 시간을 관리하는 커스텀 훅을 만들어보겠습니다:

jsx
import { useState, useEffect } from 'react';

function useCurrentTime() {
  const [currentTime, setCurrentTime] = useState(new Date());

  useEffect(() => {
    const intervalId = setInterval(() => {
      setCurrentTime(new Date());
    }, 1000);
    return () => clearInterval(intervalId);
  }, []);

  return currentTime;
}

export default useCurrentTime;

위 예제에서는 useCurrentTime이라는 커스텀 훅을 만들어, 현재 시간을 초 단위로 갱신하도록 했습니다. 이제 이 훅을 필요한 컴포넌트에서 가져와 사용할 수 있습니다.

커스텀 훅 작성 방법

커스텀 훅을 작성할 때는 몇 가지 기본 원칙을 준수해야 합니다:

  1. use로 시작하는 이름: 모든 훅은 use로 시작해야 React가 이것이 훅임을 알 수 있습니다.
  2. 다른 훅을 호출: 커스텀 훅 안에서는 기존의 훅(useState, useEffect 등)을 호출할 수 있습니다.
  3. 함수형 컴포넌트에서만 호출: 커스텀 훅은 함수형 컴포넌트 또는 다른 커스텀 훅에서만 호출될 수 있습니다.

예시: API 데이터 페칭 커스텀 훅

HTTP 요청을 통해 데이터를 가져오는 일반적인 작업을 커스텀 훅으로 만들어보겠습니다:

jsx
import { useState, useEffect } from 'react';

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);
    fetch(url)
      .then(response => {
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        return response.json();
      })
      .then(data => {
        setData(data);
        setLoading(false);
      })
      .catch(error => {
        setError(error);
        setLoading(false);
      });
  }, [url]);

  return { data, loading, error };
}

export default useFetch;

이제 이 useFetch 훅을 사용하면 다양한 컴포넌트에서 데이터 페칭 로직을 공유할 수 있습니다:

jsx
import React from 'react';
import useFetch from './useFetch';

function DataComponent({ url }) {
  const { data, loading, error } = useFetch(url);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h1>Fetched Data:</h1>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
}

export default DataComponent;

이와 같은 방식으로, 훅을 활용하여 코드의 중복을 줄이고, 필요한 곳에서 동일한 로직을 간편하게 재사용할 수 있습니다.

실전 팁

훅의 컴포넌트화

커스텀 훅을 작성할 때 컴포넌트의 특정 로직을 가능한 한 많이 분리하여, 훅의 독립성을 유지하려고 노력해야 합니다. 이렇게 하면 훅을 다양한 컴포넌트에서 재사용하기 쉬워집니다.

테스트와 디버깅

커스텀 훅도 다른 함수처럼 테스트가 필요합니다. Jest와 같은 테스트 프레임워크를 사용해서 훅의 동작을 검증하세요. 예를 들어, React Testing Library를 사용해 커스텀 훅을 테스트할 수 있습니다:

jsx
import { renderHook } from '@testing-library/react-hooks';
import useFetch from './useFetch';

it('should fetch data', async () => {
  const { result, waitForNextUpdate } = renderHook(() => useFetch('https://api.example.com/data'));

  await waitForNextUpdate();

  expect(result.current.data).not.toBeNull();
  expect(result.current.loading).toBe(false);
  expect(result.current.error).toBeNull();
});

로직의 일반화

커스텀 훅을 만들 때는 최대한 일반화된 로직으로 작성하여 다양한 상황에서 재사용할 수 있도록 해야 합니다. 예를 들어, useInput 훅을 만들어 다양한 형태의 입력 컨트롤을 관리할 수 있습니다:

jsx
import { useState } from 'react';

function useInput(initialValue) {
  const [value, setValue] = useState(initialValue);

  function handleChange(e) {
    setValue(e.target.value);
  }

  return {
    value,
    onChange: handleChange,
  };
}

export default useInput;

이제 <input> 컴포넌트와 함께 사용할 수 있습니다:

jsx
import React from 'react';
import useInput from './useInput';

function InputComponent() {
  const nameInput = useInput('');

  return (
    <div>
      <label>
        Name:
        <input type=