React Hooks 프로젝트에 도입하며 겪은 시행착오

배경

작년 10월 React 16.8이 정식 릴리즈되면서 Hooks가 안정화되었다. 몇 달간 토이 프로젝트로 테스트해본 결과 괜찮다고 판단해 실무 프로젝트에 도입하기로 했다.

useState의 함정

Class 컴포넌트의 setState와 달리 useState는 기존 state를 자동으로 병합하지 않는다. 이걸 간과해서 처음엔 state 일부가 날아가는 버그를 만들었다.

// 잘못된 방식
const [form, setForm] = useState({ name: '', email: '' });
setForm({ name: 'John' }); // email이 사라짐

// 올바른 방식
setForm(prev => ({ ...prev, name: 'John' }));

useEffect 의존성 배열

ESLint의 exhaustive-deps 규칙을 켜고 나서 놓치던 의존성들이 많이 드러났다. 특히 함수를 의존성에 넣어야 할 때 useCallback으로 감싸지 않으면 무한 루프가 발생했다.

const fetchData = useCallback(async () => {
  const result = await api.get(`/data/${id}`);
  setData(result);
}, [id]);

useEffect(() => {
  fetchData();
}, [fetchData]);

Custom Hooks로 로직 분리

Hooks의 진짜 장점은 로직 재사용이었다. 폼 핸들링, API 호출 같은 반복 패턴을 custom hook으로 추출하니 컴포넌트가 훨씬 간결해졌다.

function useForm(initialValues) {
  const [values, setValues] = useState(initialValues);
  
  const handleChange = (e) => {
    setValues(prev => ({
      ...prev,
      [e.target.name]: e.target.value
    }));
  };
  
  return [values, handleChange];
}

소감

Class 컴포넌트 대비 코드량이 20~30% 줄었고, lifecycle 관련 버그도 줄어들었다. 다만 팀원들이 익숙해지는 데 시간이 필요했다. 새 프로젝트는 Hooks로 시작하되, 레거시는 점진적으로 마이그레이션하는 전략으로 가기로 했다.

React Hooks 프로젝트에 도입하며 겪은 시행착오