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

도입 배경

올해 2월 React 16.8에서 정식 출시된 Hooks를 드디어 실무에 적용하기로 결정했다. 기존 Class 컴포넌트 기반 대시보드 프로젝트의 일부 모듈부터 점진적으로 전환하는 방식을 택했다.

useEffect의 의존성 배열 이해 부족

가장 먼저 마주친 문제는 useEffect의 의존성 배열이었다. 초기에는 componentDidMount처럼 사용하려고 빈 배열만 넣었는데, 클로저 문제로 stale state를 참조하는 버그가 발생했다.

const [count, setCount] = useState(0);

useEffect(() => {
  const timer = setInterval(() => {
    // 항상 0을 참조하는 문제
    setCount(count + 1);
  }, 1000);
  return () => clearInterval(timer);
}, []); // count를 의존성에 추가하지 않음

해결책은 함수형 업데이트를 사용하는 것이었다.

setCount(prev => prev + 1);

Custom Hook으로 로직 분리

Hooks의 진짜 장점은 로직 재사용이었다. API 호출 패턴을 Custom Hook으로 추출하니 코드가 훨씬 깔끔해졌다.

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(data => {
        setData(data);
        setLoading(false);
      });
  }, [url]);

  return { data, loading };
}

성능 최적화: useMemo와 useCallback

무분별하게 Hooks를 사용하다 보니 불필요한 리렌더링이 발생했다. 특히 자식 컴포넌트에 전달하는 콜백 함수를 매번 새로 생성하는 실수가 잦았다. useCallback으로 메모이제이션하니 해결됐다.

소감

Class 컴포넌트에 비해 코드량이 30% 정도 줄었고, HOC 지옥에서 벗어났다. 하지만 러닝 커브가 생각보다 있었다. 팀원들과 코드 리뷰를 통해 Best Practice를 정립하는 과정이 필요했다. 앞으로 신규 컴포넌트는 모두 Hooks로 작성할 예정이다.