React – 상태 관리와 Hooks – 커스텀 Hooks – 1 – 공통 로직 분리 및 재사용 예시

React – 상태 관리와 Hooks – 커스텀 Hooks – 1 – 공통 로직 분리 및 재사용 예시

React – 상태 관리와 Hooks – 커스텀 Hooks – 1 – 공통 로직 분리 및 재사용 예시

안녕하세요! 😊
이번에는 커스텀 훅을 어떻게 실제로 활용해서 공통 로직을 분리하고 재사용할 수 있는지 다양한 실전 예시 중심으로 알려드릴게요!
React 프로젝트가 커지면 똑같은 useEffect, useState 로직이 이곳저곳에 반복되면서 코드가 지저분해지고 관리도 힘들어지잖아요?
그럴 때 커스텀 훅은 정말 구세주처럼 등장한답니다! 💡

오늘은 대표적인 예시들을 통해 “아~ 이렇게 쓰는구나!” 감을 확실히 잡아보세요!


1. 입력값 상태 관리 로직 분리 (useInput)

💬 어떤 문제?

React에서 input 요소를 사용할 때마다 항상 이런 코드를 반복하게 돼요:

const [value, setValue] = useState('');
const handleChange = (e) => setValue(e.target.value);

👉 이렇게 똑같은 로직을 여러 input마다 반복하는 건 비효율적이죠!

✅ 커스텀 훅으로 분리

import { useState } from 'react';

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

  const onChange = (e) => {
    setValue(e.target.value);
  };

  const reset = () => setValue(initialValue);

  return { value, onChange, reset };
}

🎯 사용 예시

function LoginForm() {
  const username = useInput('');
  const password = useInput('');

  return (
    <div>
      <input type="text" placeholder="아이디" {...username} />
      <input type="password" placeholder="비밀번호" {...password} />
      <button onClick={() => {
        console.log(username.value, password.value);
        username.reset();
        password.reset();
      }}>로그인</button>
    </div>
  );
}

🧠 useInput 하나로 깔끔하게 정리됐죠?
비유하자면 input을 다루는 매크로 키보드를 만든 거예요!


2. API 요청 로직 분리 (useFetch)

💬 어떤 문제?

컴포넌트 안에서 fetch 요청을 만들다 보면 중복이 많고, 로딩/에러 관리도 불편하죠.

✅ 커스텀 훅으로 분리

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(res => {
        if (!res.ok) throw new Error('서버 응답 오류');
        return res.json();
      })
      .then(json => {
        setData(json);
        setError(null);
      })
      .catch(err => setError(err))
      .finally(() => setLoading(false));
  }, [url]);

  return { data, loading, error };
}

🎯 사용 예시

function UserList() {
  const { data, loading, error } = useFetch('https://jsonplaceholder.typicode.com/users');

  if (loading) return <p>불러오는 중...</p>;
  if (error) return <p>에러 발생: {error.message}</p>;

  return (
    <ul>
      {data.map(user => <li key={user.id}>{user.name}</li>)}
    </ul>
  );
}

💡 useFetch를 만들면 다른 컴포넌트에서도 동일한 방식으로 간편하게 데이터 불러오기 가능!


3. 디바운싱 처리 (useDebounce)

💬 어떤 문제?

검색창처럼 입력이 바뀔 때마다 API를 부르면 성능이 안 좋아요.
디바운싱은 입력 후 일정 시간 동안 입력이 없을 때만 동작하는 방식입니다.

✅ 커스텀 훅으로 분리

import { useState, useEffect } from 'react';

function useDebounce(value, delay = 500) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const timeout = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => clearTimeout(timeout);
  }, [value, delay]);

  return debouncedValue;
}

🎯 사용 예시

function SearchInput() {
  const [input, setInput] = useState('');
  const debounced = useDebounce(input, 1000);

  useEffect(() => {
    if (debounced) {
      console.log('검색 요청!', debounced);
      // API 호출 가능
    }
  }, [debounced]);

  return <input value={input} onChange={e => setInput(e.target.value)} />;
}

🧠 타자를 치는 도중에는 요청을 하지 않고, 입력이 멈췄을 때만 작동!


4. 이전 값 추적 (usePrevious)

💬 어떤 문제?

리렌더링 전 값을 비교하거나 기록하고 싶은 경우가 있어요.

✅ 커스텀 훅으로 분리

import { useEffect, useRef } from 'react';

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  }, [value]);

  return ref.current;
}

🎯 사용 예시

function Counter() {
  const [count, setCount] = useState(0);
  const prev = usePrevious(count);

  return (
    <div>
      <p>현재 값: {count}</p>
      <p>이전 값: {prev}</p>
      <button onClick={() => setCount(c => c + 1)}>+1</button>
    </div>
  );
}

🌱 이전 값을 기억해서 변경 전후 비교가 가능해져요!


5. 외부 클릭 감지 (useOutsideClick)

💬 어떤 문제?

모달, 드롭다운 같은 컴포넌트는 바깥 클릭 시 닫혀야 하는 경우가 많아요.

✅ 커스텀 훅으로 분리

import { useEffect } from 'react';

function useOutsideClick(ref, callback) {
  useEffect(() => {
    const handleClick = (e) => {
      if (ref.current && !ref.current.contains(e.target)) {
        callback();
      }
    };

    document.addEventListener('mousedown', handleClick);
    return () => document.removeEventListener('mousedown', handleClick);
  }, [ref, callback]);
}

🎯 사용 예시

function Modal({ onClose }) {
  const modalRef = useRef(null);
  useOutsideClick(modalRef, onClose);

  return (
    <div ref={modalRef} className="modal">모달 내용</div>
  );
}

✨ 이걸 매번 useEffect로 작성하면 너무 복잡한데,
useOutsideClick으로 뚝딱 해결!


커스텀 훅 사용할 때 주의사항

항목 설명
이름은 use로 시작 React 규칙이므로 꼭 지켜야 함
조건문 안에서 호출 금지 훅은 항상 같은 순서로 호출돼야 함
상태 업데이트 외에 사이드 이펙트도 가능 useEffect 포함 가능
너무 많은 로직을 넣지 말고 목적별로 분리 한 훅이 너무 비대해지면 유지보수가 어려워요

마무리하며

이제 여러분은 커스텀 훅으로 어떤 로직이든 분리하고 재사용할 수 있어요!
특히 팀 프로젝트에서는 이런 커스텀 훅을 잘 만들어두면
모든 컴포넌트가 간결하고 통일된 방식으로 작성될 수 있어 정말 편리해요! 🎉

✨ 오늘의 요약

🔹 커스텀 훅은 공통된 훅 로직을 함수로 추출한 것
🔹 상태 관리, 이벤트 처리, API 요청, 디바운싱 등 다양하게 활용
🔹 코드 재사용성, 가독성, 유지보수성 모두 향상!

다음에는 더 복잡한 예제를 통해 커스텀 훅을 다른 훅과 조합해서 사용하는 방법도 다뤄볼게요!
우리 같이 계속 성장해봐요! 💪🚀

답글 남기기