React Hooks 프로젝트에 도입하면서 마주친 문제들

배경

작년 10월 React 16.8이 정식 출시되면서 Hooks가 안정화되었다. 팀 내부에서 논의 끝에 신규 기능부터 Hooks로 작성하기로 결정했고, 기존 컴포넌트도 점진적으로 마이그레이션하기로 했다.

useEffect 의존성 배열 이슈

가장 자주 마주친 문제는 useEffect의 의존성 배열이었다. Class의 componentDidUpdate와 다르게 동작해서 초반에 혼란이 있었다.

const UserProfile = ({ userId }) => {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]); // userId 변경 시에만 재실행

  return <div>{user?.name}</div>;
};

의존성 배열을 빈 배열로 두면 마운트 시에만 실행되는데, 이를 간과해서 props 변경이 반영되지 않는 버그가 몇 번 발생했다.

Custom Hook 분리

로직 재사용을 위해 Custom Hook을 만들기 시작했다. HOC나 Render Props보다 훨씬 직관적이었다.

const useAuth = () => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    checkAuth().then(user => {
      setUser(user);
      setLoading(false);
    });
  }, []);

  return { user, loading };
};

useState vs useReducer

복잡한 상태 관리가 필요한 경우 useState를 여러 개 쓰는 것보다 useReducer가 나았다. Form 컴포넌트에서 특히 유용했다.

const formReducer = (state, action) => {
  switch (action.type) {
    case 'UPDATE_FIELD':
      return { ...state, [action.field]: action.value };
    case 'RESET':
      return initialState;
    default:
      return state;
  }
};

const [formState, dispatch] = useReducer(formReducer, initialState);

마무리

Hooks 도입 후 코드량이 확실히 줄었고, 로직 재사용도 편해졌다. 다만 팀원들이 익숙해지는 데 시간이 필요했다. eslint-plugin-react-hooks를 설정해서 의존성 배열 관련 실수를 줄일 수 있었다.

React Hooks 프로젝트에 도입하면서 마주친 문제들