React Hooks 도입 후 Custom Hook 패턴 정리
배경
올해 초 React 16.8이 릴리즈되면서 Hooks가 정식 스펙이 되었다. 팀에서는 3개월 정도 관망하다가 7월부터 신규 컴포넌트에 Hooks를 적용하기 시작했다. 처음에는 useState, useEffect 정도만 사용했는데, 반복되는 패턴이 보이기 시작했고 Custom Hook으로 분리하는 게 효과적이었다.
useForm 구현
폼 상태 관리는 거의 모든 페이지에서 필요했다. 기존에는 각 컴포넌트마다 state를 선언하고 onChange 핸들러를 작성했는데, 이를 Hook으로 추상화했다.
function useForm(initialValues) {
const [values, setValues] = useState(initialValues);
const handleChange = (e) => {
const { name, value } = e.target;
setValues(prev => ({ ...prev, [name]: value }));
};
const resetForm = () => setValues(initialValues);
return { values, handleChange, resetForm };
}
사용하는 쪽에서는 훨씬 간결해졌다.
function LoginForm() {
const { values, handleChange } = useForm({ email: '', password: '' });
return (
<form>
<input name="email" value={values.email} onChange={handleChange} />
<input name="password" value={values.password} onChange={handleChange} />
</form>
);
}
useFetch 패턴
API 호출 로직도 반복이 많았다. loading, error, data 상태를 매번 선언하는 대신 Hook으로 만들었다.
function useFetch(url) {
const [state, setState] = useState({ loading: true, data: null, error: null });
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(data => setState({ loading: false, data, error: null }))
.catch(error => setState({ loading: false, data: null, error }));
}, [url]);
return state;
}
느낀 점
Custom Hook은 로직 재사용을 위한 새로운 방식이다. HOC나 render props보다 직관적이고 컴포넌트 트리도 깔끔하게 유지된다. 다만 useEffect 의존성 배열 관리가 까다로워서 ESLint의 exhaustive-deps 규칙을 켜두고 작업하는 게 필수다.
앞으로는 useLocalStorage, useDebounce 같은 유틸성 Hook도 만들어볼 예정이다.