훅이란
훅이란 클래스 컴포넌트에서 사용되던 state와 생명주기 관련 기능 등을 함수 컴포넌트에서 쓸 수 있게 하기 위해 만들어졌다.
React 16.8 버전에서 도입된 이 기능은 use로 시작하는 함수들로, useState, useEffect, useContext 등이 대표적인 예다.
훅을 사용하면 함수 컴포넌트에서도 상태 관리와 부수 효과를 처리할 수 있어 코드의 재사용성과 구성을 더 효율적으로 만들 수 있다.
Custom hook만들기 및 주의점
커스텀 훅은 React의 내장 훅을 활용하여 만드는 재사용 가능한 함수. 특별한 API가 아니라 함수의 이름이 use로 시작하는 JavaScript 함수.
hooks라는 폴더를 만들어서 모아놓는것이 일반적!
function useCustomHook(initialValue) {
const [state, setState] = useState(initialValue);
useEffect(() => {
// 원하는 로직 구현
return () => {
// 정리(cleanup) 함수
};
}, [dependencies]);
// 필요한 함수 정의
const handleChange = () => {
// 상태 변경 로직
};
return { state, handleChange }; // 외부에 노출할 값과 함수 반환
}
주의점
- 이름은 반드시 ‘use’로 시작
- React의 규칙을 따르고 lint 도구가 제대로 작동하게한다. 즉 use로 시작 안하면 최적ㅘ가 안되거나 렌더링 우선순의가 잘못처리 될 수 있다.
- 훅 호출은 컴포넌트나 다른 훅 내부에서만 해야 한다.
- 일반 함수나 조건문, 반복문 내부에서 직접 호출하면 안된다
- 의존성 배열 관리에 주의해야 한
- 불필요한 렌더링이나 무한 루프를 방지하기 위해 의존성 배열을 올바르게 설정해야한다
- 관심사 분리를 잘 해야 한다
- 하나의 커스텀 훅이 너무 많은 일을 하지 않도록 해야한다
언제 Custom hook을 만들어야할까
커스텀 훅은 이런 상황에서 만들어야 한다:
- 반복되는 로직이 여러 컴포넌트에 있을 때
- 비슷한 로직이 여러 곳에 중복된다? 커스텀 훅으로 만들어라.
- 복잡한 상태 로직을 분리해야 할 때
- 컴포넌트가 너무 복잡해졌다? 로직을 훅으로 분리해라.
- 외부 데이터와 연결할 때
- API 호출? 웹소켓? 로컬 스토리지? 커스텀 훅으로 만들어라.
- 브라우저 API를 사용할 때
- 위치 정보? 미디어 쿼리? 이벤트? 훅으로 감싸라.
- 애니메이션, 타이머, 인터벌 등을 관리할 때
- 정리(cleanup)가 필요한 로직이다? 커스텀 훅이 해결책이다.
예시 useLocalStorage: 로컬 스토리지 관리? 이 훅이 필요. useFetch: API 요청? 이 훅으로 단순화. useMediaQuery: 반응형 필요할때 useAuth: 인증 로직 훅으로 관리. useTheme: 테마 전환. 커스텀 훅은 코드 중복을 없애고 컴포넌트를 간결하게 만든다. 복잡한 로직이 있다면 커스텀 훅으로 만들어라!
참고로 useFetch같은 경우에는 많이사용해서 전형적인 소스가 존재한다
// hooks/useFetch.js
import { useState, useEffect } from "react";
function useFetch(url, options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [refetchIndex, setRefetchIndex] = useState(0);
// 데이터 다시 가져오기 함수
const refetch = () => {
setLoading(true);
setRefetchIndex((prev) => prev + 1);
};
useEffect(() => {
// AbortController로 요청 취소 가능하게 만들기
const controller = new AbortController();
const signal = controller.signal;
const fetchData = async () => {
setLoading(true);
try {
const response = await fetch(url, {
...options,
signal,
});
if (!response.ok) {
throw new Error(`에러 발생! 상태 코드: ${response.status}`);
}
const result = await response.json();
setData(result);
setError(null);
} catch (err) {
if (err.name !== "AbortError") {
setError(err.message);
setData(null);
}
} finally {
setLoading(false);
}
};
fetchData();
s;
// 클린업 함수: 컴포넌트 언마운트나 의존성 변경 시 요청 취소
return () => {
controller.abort();
};
}, [url, refetchIndex, JSON.stringify(options)]);
return { data, loading, error, refetch };
}
export default useFetch;
s;