c9u11

useEffect에 이름 붙이기

🔗아래 블로그 글에 대한 리뷰 글입니다. 글에 대한 상세한 내용은 작성하지 않으니 내용은 아래 링크를 통해 확인하시길 바랍니다. https://velog.io/@typo/name-your-effects?utm_source=substack&utm_medium=email

익명 이펙트 함수의 문제

useEffect는 정말 편리한 hook입니다.

React를 사용하지 않고 프론트를 개발할 때는 onLoad 등과 같은 이벤트들을 사용하여 initialize와 같은 동작을 수행해냈습니다.

값이 바뀔 때를 인지하기 위해서는 setter를 따로 구현하여 해당 함수 내부에 관련 로직을 작성하고는 했습니다.

React를 배우며 useState, useEffect의 편리함을 알게 되었고 자주 사용하게되었습니다.

따라서 우리는 useEffect를 정말 많은 컴포넌트에서 사용합니다.

사이드 프로젝트나 실제 현업에서 프론트 개발을 하다보면 정보 조회가 필요한 페이지를 마주치게 됩니다.

내부에서는 최소 1개 많을 때는 4-5개의 useEffect를 사용하고는 합니다.

저는 최대 3-4개를 사용해본 경험이 있습니다.

useEffect는 주로 인라인 문법을 사용하여 작성했습니다. 그렇게 배웠고 그렇게 써왔기 때문에 큰 문제를 느끼지 못했습니다.

여기서 익명 Effect 함수의 문제가 나타납니다.

대부분은 dependency를 빈 배열로 두고 작성하기에 useEffect가 무슨 일을 하는지 어느 정도 예상한 상태에서 코드를 리뷰하게 됩니다.

아마도 초기화 관련 로직이겠지, 아마도 첫 fetch를 하는 로직이겠지 머릿 속으로 이미 예상을 한 상태로 코드를 읽어 매우 잘 읽혔을 겁니다.

문제는 useEffect가 3-4개로 늘어나면서 생깁니다.

처음 코드를 봤을 때 각 useEffect의 역할이 무엇인지 알 수 없고 모든 코드를 읽어야 이해가 됩니다.

어떨 때는 하나의 useEffect에 여러 동작이 들어가기도 합니다.

아래에서 useEffect에 이름을 붙여 문제를 개선하고 다른 방법은 어떤지 확인하겠습니다.

각 useEffect의 역할 파악

useEffect에 이름을 붙이는 방법은 아래와 같습니다.

// 익명 화살표 함수 (모두가 쓰는 방식)
useEffect(() => {
  document.title = `${count} items`;
}, [count]);

// 기명 함수 표현식 (제가 권장하는 방식)
useEffect(
  function updateDocumentTitle() {
    document.title = `${count} items`;
  },
  [count],
);

useEffect를 사용하지 않아도 되는 상황이기에 위 예시는 좋은 예시라고 보기는 어렵습니다.

이름을 붙이는 방법이 어떤 식인지만 인지하면 좋을 것 같습니다.

useEffect 자체에 이름을 붙이면 각 useEffect의 역할을 쉽게 파악할 수 있습니다.

저는 원래 함수를 선언하고 useEffect에 쓸 때가 종종 있었습니다.

로직의 전체적인 흐름을 파악할 때는 편리하고 좋지만 함수 내부의 동작을 파악하기 위해서는 상단 선언부로 올라가 읽어야하는 단점이 존재했습니다. 어쩌면 이 방법은 useEffect에 많은 역할을 부여했기에 전체적인 흐름을 파악해야했을 수 있습니다.

책임 최소화하기

이름을 붙이다보면 어떻게 이름을 붙여야하지 고민이 들 때가 있을 겁니다.

아래의 예시를 보고 이름을 붙여보세요.

useEffect(() => {
  const handleResize = () => setWidth(window.innerWidth);
  window.addEventListener("resize", handleResize);

  if (user?.preferences?.theme) {
    document.body.className = user.preferences.theme;
  }

  return () => window.removeEventListener("resize", handleResize);
}, [user?.preferences?.theme]);

저라면 applyTheme 또는 handleDesign 정도로 붙였을 것 같습니다.

위 네이밍은 너무 추상적이거나 함수 내에 관련 없는 코드가 포함되어 있다고 생각이 들 수 있습니다.

블로그에서는 syncWidthAndApplyTheme 정도로 설명했습니다.

이름에 And 또는 Also 등이 들어간다면 함수의 역할 자체가 큰 상황이라고 볼 수 있습니다.

어쩌면 이름을 붙이는 방법이 주석을 다는 것보다 효율적이고 관리가 잘 된다고 볼 수 있을 겁니다.

또한 useCallback, useMemo 등 여러 hook에서 익명함수보다 이름을 붙여 책임을 최소화하는 것이 가능합니다.

필요 없는 useEffect 지우기

이제 이름을 모두 정했고 코드를 한 눈에 살펴볼 수 있습니다.

이름을 지을 때부터 애매한 네이밍을 했던 useEffect가 가장 기억에 남고 눈에 띌 겁니다.

그리고 이름만 보고도 필요없는지 확인할 수 있을겁니다.

updateStateBasedOnOtherState라는 이름은 다른 state를 base로 가지고있는 state를 업데이트한다는 의미를 가지고있습니다.

// 아마 이건 필요 없습니다
useEffect(
  function syncFullName() {
    setFullName(`${firstName} ${lastName}`);
  },
  [firstName, lastName],
);

// 그냥 파생하면 됩니다
const fullName = `${firstName} ${lastName}`;

위 예시를 보면 충분히 필요 없는 코드인 게 한 눈에 보입니다.

예시가 너무 쉽고 절대 저렇게 하지 않는다는 생각을 가지고 계신다면 내 코드를 다시 한 번 돌아볼 필요가 있습니다.

저도 저런 동작을 꽤나 많이 써왔던 것 같습니다.

위 동작에서는 리액트가 컴포넌트를 렌더링하고 useEffect가 실행되어 setFullName을 호출하고 다시 렌더링이 일어나는 참사가 벌어집니다.

또한 처음 fullName은 초기값이거나 이전 값을 유지할테니 그 값도 잠깐 보입니다.

하지만 useEffect를 사용하지않으면 이러한 문제를 완전히 없앨 수 있습니다.

아래의 예시는 form data를 초기화할 때 사용합니다.

주석에 적힌 것처럼 이벤트 핸들러에서 처리해야할 항목입니다.

// 이것도 아마 필요 없습니다
useEffect(
  function resetFormOnSubmit() {
    if (submitted) {
      setName("");
      setEmail("");
      setSubmitted(false);
    }
  },
  [submitted],
);

// 이벤트 핸들러에 넣으세요
function handleSubmit() {
  submitForm({ name, email });
  setName("");
  setEmail("");
}

이름 붙이기 vs 커스텀 훅

저는 아직 커스텀 훅을 잘 사용하지 못합니다.

fetch와 관련한 소스 코드와 같이 특정 상황에서 주로 사용하는 것 같습니다.

여러 상황에서 커스텀 훅으로 만들어보고 재사용하는 경험을 해보면 좋을 것 같습니다.

하지만 모든 useEffect를 커스텀 훅으로 변환할 수 없다는 사실은 알고있습니다.

불필요한 분리가 되고 재사용이 되지않는다면 커스텀 훅은 의미가 없습니다.

따라서 상황에 따라 커스텀 훅을 사용할 때가 있다고 보면 좋을 것 같습니다.

또한 커스텀 훅 내에서도 아래 예시와 같이 이름 붙이기를 사용할 수 있습니다.

function useWindowWidth() {
  const [width, setWidth] = useState(
    typeof window !== "undefined" ? window.innerWidth : 0,
  );

  useEffect(function trackWindowWidth() {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener("resize", handleResize);
    return () => window.removeEventListener("resize", handleResize);
  }, []);

  return width;
}

리뷰

술술 읽히는 코드가 저는 가장 클린한 코드라고 생각하고 지향하고 있습니다.

각종 hook을 사용할 때는 그동안 써왔던 방식이 Default가 되어 인지를 못한 것 같습니다.

저 뿐만 아니라 많은 사람들과 AI도 익명 함수를 기본으로 써왔고 앞으로도 쓰겠지만 블로그에서 제시한 방법은 여러 방면에서 도움이되는 코드 작성법이라고 생각합니다.

날이 갈수록 AI가 발전하며 구현은 쉬워집니다. 하지만 이러한 변화는 쉽게 찾아오지 않습니다.

따라서 개발자가 키워야할 역량 중 하나라고 생각합니다.