Vue의 Composable · React의 Custom Hook을 사용하는 이유와 분리 기준
목차
· 로직을 Composable · Custom Hook으로 나눠 사용을 하는 이유는 무엇일까?
· 그러면 어떤 기준으로 분리를 해요?
· Composable · Custom Hook과 유틸함수의 차이는 무엇일까?
회사에서 개발을 하다보면 공통된 로직이나 다양한 관심사를 가진 로직이 존재한다면 무의식적으로 쪼개는 버릇이 생겼어요. 몸이 이에 적응되어 어느샌가부터 이유도 모르고 쪼개는 버릇이 생기다보니 근본적으로 어떤 기준으로 관심사 분리를 해야하는지, 그리고 더 근본적으로 가다보니 왜 관심사 분리를 해야할까에 대한 생각들이 들더라구요. 그래서 오늘은 커스텀 훅을 사용하는 이유와 더불어 분리하는 기준에 대해서 한번 복기 차 포스팅을 해보려고 해요.
로직을 Composable · Custom Hook으로 나눠 사용을 하는 이유는 무엇일까?
Vue의 Composable의 React의 Custom Hook은 로직을 재사용하기 위한 패턴이에요. 컴포넌트 간의 공통으로 사용하는 useEffect · useState와 같은 상태 관련 로직이 존재한다면, 이를 커스텀 훅으로 분리하여 여러 컴포넌트에서 재사용할 수 있어요. 컴포넌트에 너무 많은 상태 관리와 부수 효과 (Side Effect)와 같은 로직들이 몰리게 되면 가독성이 떨어질 뿐만 아니라, 하나의 컴포넌트에서 다중 역할을 하다보니 여러 역할과의 의존성이 높을 뿐더러 많은 책임들을 가질 수 있어요.
Composable 또는 Custom Hook을 통해 로직을 분리하게 된다면 컴포넌트는 UI에 집중하고, 혹은 로직에 집중할 수 있어 더 깔끔한 구조를 만들 수 있답니다. 또한 로직이 커스텀 훅으로 분리가 되어있다면, 해당 로직만을 따로 테스트하기가 쉬워져요. 테스트 코드에서도 UI 렌더링 없이 로직 단위로 테스트를 할 수 있어요.
그러면 어떤 기준으로 분리를 해요?
[1] 여러 컴포넌트에서 중복되는 로직이 발견될 때
fetch, pagination, form validation, debounce와 같은 로직 등
const [input, setInput] = useState('');
const [debouncedInput, setDebouncedInput] = useState(input);
useEffect(() => {
const timeout = setTimeout(() => setDebouncedInput(input), 500);
return () => clearTimeout(timeout);
}, []);
위 코드는 useDebounce(input, 500)과 같은 커스텀 훅으로 분리를 할 수 있을 것 같아요.
[2] 여러 부수 효과 (Side Effect)나 비즈니스 로직이 복잡해질 때
scroll, resize, observer, intersection, analytics, 권한 체크 등
useEffect(() => {
const onScroll = () => { ... };
window.addEventListener('scroll', onScroll);
return () => window.removeListener('scroll', onScroll);
}. []);
[3] 컴포넌트 내부의 훅이 너무 많거나, 로직이 엉켜있을 때
상태 값, 효과, 이벤트 핸들러가 엉켜있어 복잡하다면, 관련된 훅들을 묶어 정리할 수 있어요.
const { isOpen, toggle, close } = useToggle();
const { value, onChange } = useInput();
[4] UI와 로직을 명확히 분리하고 싶을 때
뷰 단에서는 레이아웃에 관련된 부분만을 집중하고, 훅에서는 관련 로직에 대해서만 집중을 해요. 이로써 관심사 분리를 통해 코드 가독성을 한층 향상시킬 수 있어요.
Composable · Custom Hook과 유틸 함수의 차이는 무엇일까?
유틸 함수와 Composable · Custom Hook 모두 둘 다 코드 재사용성을 위한 패턴이지만, 역할과 사용하는 맥락에서 차이를 확인할 수 있어요.
[1] 유틸 함수
- 특정 기능을 수행하는 일반 함수
- 로직 · 계산 · 형식 변환 등 순수 기능을 수행해요
- React 상태와 같은 관련 훅들은 사용하지 않아요
- 아무 곳에서나 호출이 가능해요
- formatDate, isEmpty, sum과 같은 유틸 함수들을 만들 수 있어요.
// utils.date.ts
export function formatDate(date: Date): string {
return date.toISOString().split('T')[0];
}
// use case
const formatted = formatDate(new Date());
[2] Composable · Custom Hook
- React 상태와 같은 useState · useEffect 및 Vue의 Ref · Watch와 같은 상태 관련 훅을 사용하는 함수예요.
- React 또는 Vue의 생명주기나 상태 관련 로직을 재사용해요
- 유틸 함수처럼 아무 곳에서나 호출이 가능하지 않고, 컴포넌트 또는 다른 훅 내부에서만 사용이 가능해요
- useFetch(), useToggle(), useScroll()과 같은 커스텀 훅을 만들 수 있어요
// hooks/useToggle.ts
export function useToggle(initial = false) {
const [state, setState] = useState(initial);
const toggle = () => setState(prev => !prev);
return [state, toggle] as const;
}
// use case
const [isOpen, toggleOpen] = useToggle();