커스텀 훅으로 React 애플리케이션의 재사용 가능한 로직의 힘을 발휘해 보세요. 더 깔끔하고 유지보수하기 쉬운 코드를 위해 커스텀 훅을 만들고 활용하는 방법을 알아보세요.
커스텀 훅: React의 재사용 가능한 로직 패턴
React 훅은 함수형 컴포넌트에 상태 및 생명주기 기능을 도입하여 우리가 React 컴포넌트를 작성하는 방식에 혁명을 일으켰습니다. 훅이 제공하는 많은 이점 중에서, 커스텀 훅은 여러 컴포넌트에 걸쳐 로직을 추출하고 재사용하는 강력한 메커니즘으로 두드러집니다. 이 블로그 게시물에서는 커스텀 훅의 세계를 깊이 파고들어 그 이점, 생성 및 실제 예제를 통한 사용법을 탐구할 것입니다.
커스텀 훅이란 무엇인가?
본질적으로 커스텀 훅은 'use'라는 단어로 시작하고 다른 훅을 호출할 수 있는 JavaScript 함수입니다. 이를 통해 컴포넌트 로직을 재사용 가능한 함수로 추출할 수 있습니다. 이는 렌더 프롭(render props), 고차 컴포넌트(higher-order components) 또는 다른 복잡한 패턴에 의존하지 않고 컴포넌트 간에 상태 저장 로직, 부수 효과 또는 기타 복잡한 동작을 공유하는 강력한 방법입니다.
커스텀 훅의 주요 특징:
- 이름 규칙: 커스텀 훅은 반드시 'use'라는 단어로 시작해야 합니다. 이는 React에게 해당 함수가 훅을 포함하고 있으며 훅의 규칙을 따라야 함을 알립니다.
- 재사용성: 주된 목적은 재사용 가능한 로직을 캡슐화하여 컴포넌트 간에 기능을 쉽게 공유할 수 있도록 하는 것입니다.
- 상태 저장 로직: 커스텀 훅은
useState
훅을 사용하여 자체 상태를 관리할 수 있으며, 이를 통해 복잡한 상태 저장 동작을 캡슐화할 수 있습니다. - 부수 효과: 또한
useEffect
훅을 사용하여 부수 효과를 수행할 수 있어 외부 API와의 통합, 데이터 가져오기 등을 가능하게 합니다. - 조합 가능성: 커스텀 훅은 다른 훅을 호출할 수 있어 더 작고 집중된 훅들을 조합하여 복잡한 로직을 구축할 수 있습니다.
커스텀 훅 사용의 이점
커스텀 훅은 React 개발에서 여러 가지 중요한 이점을 제공합니다:
- 코드 재사용성: 가장 명백한 이점은 여러 컴포넌트에 걸쳐 로직을 재사용할 수 있다는 것입니다. 이는 코드 중복을 줄이고 더 DRY(Don't Repeat Yourself)한 코드베이스를 촉진합니다.
- 가독성 향상: 복잡한 로직을 별도의 커스텀 훅으로 추출함으로써 컴포넌트가 더 깔끔해지고 이해하기 쉬워집니다. 핵심 컴포넌트 로직은 UI 렌더링에 집중하게 됩니다.
- 유지보수성 강화: 로직이 커스텀 훅에 캡슐화되어 있으면 변경 및 버그 수정을 한 곳에서 적용할 수 있어 여러 컴포넌트에서 오류를 발생시킬 위험이 줄어듭니다.
- 테스트 용이성: 커스텀 훅은 독립적으로 쉽게 테스트할 수 있어 재사용 가능한 로직이 이를 사용하는 컴포넌트와 무관하게 올바르게 작동하는지 확인할 수 있습니다.
- 컴포넌트 단순화: 커스텀 훅은 컴포넌트를 정리하여 덜 장황하게 만들고 주요 목적에 더 집중하도록 돕습니다.
나만의 첫 커스텀 훅 만들기
창 크기를 추적하는 훅이라는 실제 예제를 통해 커스텀 훅의 생성을 설명해 보겠습니다.
예제: useWindowSize
이 훅은 브라우저 창의 현재 너비와 높이를 반환합니다. 또한 창 크기가 조절될 때 이 값들을 업데이트합니다.
import { useState, useEffect } from 'react';
function useWindowSize() {
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight,
});
useEffect(() => {
function handleResize() {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
}
window.addEventListener('resize', handleResize);
// 정리 시 이벤트 리스너 제거
return () => window.removeEventListener('resize', handleResize);
}, []); // 빈 배열은 마운트 시에만 이펙트가 실행되도록 보장합니다
return windowSize;
}
export default useWindowSize;
설명:
- 필요한 훅 가져오기: React에서
useState
와useEffect
를 가져옵니다. - 훅 정의하기: 이름 규칙에 따라
useWindowSize
라는 함수를 만듭니다. - 상태 초기화하기:
useState
를 사용하여windowSize
상태를 창의 초기 너비와 높이로 초기화합니다. - 이벤트 리스너 설정하기:
useEffect
를 사용하여 창에 resize 이벤트 리스너를 추가합니다. 창 크기가 조절되면handleResize
함수가windowSize
상태를 업데이트합니다. - 정리(Cleanup): 컴포넌트가 마운트 해제될 때 이벤트 리스너를 제거하기 위해
useEffect
에서 정리 함수를 반환합니다. 이는 메모리 누수를 방지합니다. - 값 반환하기: 훅은 창의 현재 너비와 높이를 포함하는
windowSize
객체를 반환합니다.
컴포넌트에서 커스텀 훅 사용하기
이제 커스텀 훅을 만들었으니 React 컴포넌트에서 어떻게 사용하는지 알아봅시다.
import React from 'react';
import useWindowSize from './useWindowSize';
function MyComponent() {
const { width, height } = useWindowSize();
return (
창 너비: {width}px
창 높이: {height}px
);
}
export default MyComponent;
설명:
- 훅 가져오기:
useWindowSize
커스텀 훅을 가져옵니다. - 훅 호출하기: 컴포넌트 내에서
useWindowSize
훅을 호출합니다. - 값에 접근하기: 반환된 객체를 구조 분해하여
width
와height
값을 얻습니다. - 값 렌더링하기: 컴포넌트의 UI에 너비와 높이 값을 렌더링합니다.
useWindowSize
를 사용하는 모든 컴포넌트는 창 크기가 변경될 때 자동으로 업데이트됩니다.
더 복잡한 예제
커스텀 훅의 더 고급 사용 사례를 살펴보겠습니다.
예제: useLocalStorage
이 훅을 사용하면 로컬 스토리지에서 데이터를 쉽게 저장하고 검색할 수 있습니다.
import { useState, useEffect } from 'react';
function useLocalStorage(key, initialValue) {
// 값을 저장할 상태
// 로직이 한 번만 실행되도록 초기 값을 useState에 전달
const [storedValue, setStoredValue] = useState(() => {
try {
// 키로 로컬 스토리지에서 가져오기
const item = window.localStorage.getItem(key);
// 저장된 json을 파싱하거나, 없으면 초기 값을 반환
return item ? JSON.parse(item) : initialValue;
} catch (error) {
// 오류 발생 시에도 초기 값을 반환
console.log(error);
return initialValue;
}
});
// useState의 setter 함수를 래핑하여...
// ...새 값을 localStorage에 유지하는 버전을 반환합니다.
const setValue = (value) => {
try {
// 값이 함수일 수 있도록 하여 useState와 동일한 API를 가짐
const valueToStore = value instanceof Function ? value(storedValue) : value;
// 로컬 스토리지에 저장
window.localStorage.setItem(key, JSON.stringify(valueToStore));
// 상태 저장
setStoredValue(valueToStore);
} catch (error) {
// 더 고급 구현에서는 오류 사례를 처리할 것입니다
console.log(error);
}
};
useEffect(() => {
try {
const item = window.localStorage.getItem(key);
setStoredValue(item ? JSON.parse(item) : initialValue);
} catch (error) {
console.log(error);
}
}, [key, initialValue]);
return [storedValue, setValue];
}
export default useLocalStorage;
사용법:
import React from 'react';
import useLocalStorage from './useLocalStorage';
function MyComponent() {
const [name, setName] = useLocalStorage('name', 'Guest');
return (
안녕하세요, {name}님!
setName(e.target.value)}
/>
);
}
export default MyComponent;
예제: useFetch
이 훅은 API에서 데이터를 가져오는 로직을 캡슐화합니다.
import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP 오류! 상태: ${response.status}`);
}
const json = await response.json();
setData(json);
setLoading(false);
} catch (error) {
setError(error);
setLoading(false);
}
}
fetchData();
}, [url]);
return { data, loading, error };
}
export default useFetch;
사용법:
import React from 'react';
import useFetch from './useFetch';
function MyComponent() {
const { data, loading, error } = useFetch('https://jsonplaceholder.typicode.com/todos/1');
if (loading) return 로딩 중...
;
if (error) return 오류: {error.message}
;
return (
제목: {data.title}
완료 여부: {data.completed ? '예' : '아니오'}
);
}
export default MyComponent;
커스텀 훅을 위한 모범 사례
커스텀 훅이 효과적이고 유지보수 가능하도록 하려면 다음 모범 사례를 따르십시오:
- 집중된 상태 유지: 각 커스텀 훅은 단일하고 잘 정의된 목적을 가져야 합니다. 너무 많은 것을 하려는 지나치게 복잡한 훅을 만들지 마십시오.
- 훅 문서화: 각 커스텀 훅에 대해 목적, 입력 및 출력을 설명하는 명확하고 간결한 문서를 제공하십시오.
- 훅 테스트: 커스텀 훅에 대한 단위 테스트를 작성하여 올바르고 안정적으로 작동하는지 확인하십시오.
- 설명적인 이름 사용: 커스텀 훅의 목적을 명확하게 나타내는 설명적인 이름을 선택하십시오.
- 오류를 적절하게 처리: 예상치 못한 동작을 방지하고 유용한 오류 메시지를 제공하기 위해 커스텀 훅 내에 오류 처리를 구현하십시오.
- 재사용성 고려: 재사용성을 염두에 두고 커스텀 훅을 설계하십시오. 여러 컴포넌트에서 사용할 수 있을 만큼 일반적으로 만드십시오.
- 과도한 추상화 피하기: 컴포넌트 내에서 쉽게 처리할 수 있는 간단한 로직에 대해 커스텀 훅을 만들지 마십시오. 진정으로 재사용 가능하고 복잡한 로직만 추출하십시오.
피해야 할 일반적인 함정
- 훅의 규칙 위반: 항상 커스텀 훅 함수의 최상위 수준에서 훅을 호출하고 React 함수 컴포넌트나 다른 커스텀 훅에서만 호출하십시오.
- useEffect에서 의존성 무시: 부실한 클로저(stale closures) 및 예상치 못한 동작을 방지하기 위해
useEffect
훅의 의존성 배열에 모든 필요한 의존성을 포함해야 합니다. - 무한 루프 생성:
useEffect
훅 내에서 상태를 업데이트할 때 주의하십시오. 이는 쉽게 무한 루프로 이어질 수 있습니다. 업데이트가 조건부이고 의존성 변경에 기반하는지 확인하십시오. - 정리(Cleanup) 잊기: 메모리 누수를 방지하기 위해 이벤트 리스너를 제거하고, 구독을 취소하고, 기타 정리 작업을 수행하기 위해 항상
useEffect
에 정리 함수를 포함하십시오.
고급 패턴
커스텀 훅 조합하기
커스텀 훅을 함께 조합하여 더 복잡한 로직을 만들 수 있습니다. 예를 들어, useLocalStorage
훅과 useFetch
훅을 결합하여 가져온 데이터를 로컬 스토리지에 자동으로 유지할 수 있습니다.
훅 간의 로직 공유
여러 커스텀 훅이 공통 로직을 공유하는 경우, 해당 로직을 별도의 유틸리티 함수로 추출하여 두 훅 모두에서 재사용할 수 있습니다.
커스텀 훅과 Context 함께 사용하기
커스텀 훅은 React Context와 함께 사용하여 전역 상태에 접근하고 업데이트할 수 있습니다. 이를 통해 애플리케이션의 전역 상태를 인식하고 상호 작용할 수 있는 재사용 가능한 컴포넌트를 만들 수 있습니다.
실제 사례
다음은 실제 애플리케이션에서 커스텀 훅을 사용할 수 있는 몇 가지 예입니다:
- 폼 유효성 검사: 폼 상태, 유효성 검사 및 제출을 처리하는
useForm
훅을 만듭니다. - 인증: 사용자 인증 및 권한 부여를 관리하는
useAuth
훅을 구현합니다. - 테마 관리: 다른 테마(밝은, 어두운 등) 간에 전환하는
useTheme
훅을 개발합니다. - 지리적 위치: 사용자의 현재 위치를 추적하는
useGeolocation
훅을 구축합니다. - 스크롤 감지: 사용자가 페이지의 특정 지점까지 스크롤했는지 감지하는
useScroll
훅을 만듭니다.
예제 : 지도 또는 배달 서비스와 같은 다문화 애플리케이션을 위한 useGeolocation 훅
import { useState, useEffect } from 'react';
function useGeolocation() {
const [location, setLocation] = useState({
latitude: null,
longitude: null,
error: null,
});
useEffect(() => {
if (!navigator.geolocation) {
setLocation({
latitude: null,
longitude: null,
error: '이 브라우저에서는 Geolocation이 지원되지 않습니다.',
});
return;
}
const watchId = navigator.geolocation.watchPosition(
(position) => {
setLocation({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
error: null,
});
},
(error) => {
setLocation({
latitude: null,
longitude: null,
error: error.message,
});
}
);
return () => navigator.geolocation.clearWatch(watchId);
}, []);
return location;
}
export default useGeolocation;
결론
커스텀 훅은 더 깔끔하고, 재사용 가능하며, 유지보수하기 쉬운 React 코드를 작성하기 위한 강력한 도구입니다. 복잡한 로직을 커스텀 훅에 캡슐화함으로써 컴포넌트를 단순화하고, 코드 중복을 줄이며, 애플리케이션의 전체 구조를 개선할 수 있습니다. 커스텀 훅을 받아들이고 그 잠재력을 발휘하여 더 견고하고 확장 가능한 React 애플리케이션을 구축하십시오.
기존 코드베이스에서 여러 컴포넌트에 걸쳐 로직이 반복되는 영역을 식별하는 것부터 시작하십시오. 그런 다음 해당 로직을 커스텀 훅으로 리팩터링하십시오. 시간이 지남에 따라 개발 프로세스를 가속화하고 코드 품질을 향상시킬 재사용 가능한 훅 라이브러리를 구축하게 될 것입니다.
모범 사례를 따르고, 일반적인 함정을 피하며, 고급 패턴을 탐색하여 커스텀 훅을 최대한 활용하는 것을 기억하십시오. 연습과 경험을 통해 당신은 커스텀 훅의 달인이자 더 효과적인 React 개발자가 될 것입니다.