main-logo

useEffect 최대한 피해보자

useEffect 사용을 지양하는 방법

profile
nocturne9no1
2023년 12월 25일 · 0 분 소요

들어가며

useEffect 는 React에서 과거 life cycle 시스템을 더욱 잘 다룰 수 있게 해준 훅입니다. 하지만 useEffect의 사용은 지양되어야 합니다. 대부분의 경우 useEffect 를 잘못 사용하고 있고 이를 제거하는 작업이 필요합

니다. 잘못된 소리처럼 들릴 수 있지만 실제 프로젝트에서 useEffect 를 어떻게 사용해야 하는 지, 쉽게 사용할 수 있을 지 살펴보도록 하겠습니다.

 

본론

useEffect 를 한 컴포넌트에 여러개 사용해야 할 경우가 있습니다. 예를 들어보겠습니다.

const [a, setA] = useState(0);
const [b, setB] = useState(0);
​
useEffect(() => {
  setA((prev) => prev + 1)
}, [a])
​
useEffect(() => {
  setB((prev) => prev + 1)
}, [b])

위와 같은 경우, 한 파일 안에서 같은 훅(useEffect)을 통해 로직을 관리하게 되면서 가독성이 떨어지게 되고 그로 인해 유지보수도 어려워지게 됩니다. 지금 같은 경우는 짧은 예시지만, useEffect의 callback 함수로 큰 로직이 들어온다면 지나치게 큰 컴포넌트가 탄생하게 됩니다. 이를 위해서 이를 커스텀 훅으로 분리하여 관리할 수 있습니다.

 

또 다른 useEffect를 잘못 사용하는 예시를 살펴보겠습니다. 만약 컴포넌트에서 성과 이름의 두 상태값을 가지고 이를 풀 네임으로 합치고 싶다고 가정해봅시다. 성이나 이름이 바뀔 때마다 풀 네임을 업데이트 하고 싶을 때 다음과 같이 잘못 사용할 수 있습니다. 코드는 리액트 공식문서를 참조했습니다.

function Form() {
  const [firstName, setFirstName] = useState('Taylor');
  const [lastName, setLastName] = useState('Swift');
​
  // 🔴 Avoid: 불필요한 상태와 불필요한 effect가 발생합니다.
  const [fullName, setFullName] = useState('');
  useEffect(() => {
    setFullName(firstName + ' ' + lastName);
  }, [firstName, lastName]);
  // ...
}

이를 아래와 같이 변경하면 문제는 해결됩니다.

function Form() {
  const [firstName, setFirstName] = useState('Taylor');
  const [lastName, setLastName] = useState('Swift');
  // ✅ Good: 렌더링 중에 계산됩니다.
  const fullName = firstName + ' ' + lastName;
  // ...
}

무언가 이미 존재하는 props나 state를 통해 계산되는 것이 있을 때 그걸 상태에 넣지 말아야 합니다. 대신에 렌더링 동안 계산하세요. 이는 코드를 더 빠르게 하고 간단하게 하며 에러 가능성 또한 줄여줍니다.

 

다음으로 prop이 변경될 때 모든 state를 리셋하는 방법에 대해 알아봅시다. 우선 코드를 보여드리겠습니다.

export default function ProfilePage({ userId }) {
  const [comment, setComment] = useState('');
​
  // 🔴 Avoid: prop 변경에 따른 state 리셋 effect
  useEffect(() => {
    setComment('');
  }, [userId]);
  // ...
}

ProfilePage 컴포넌트는 userId prop을 전달받고 있습니다. 이 페이지는 댓글 입력을 포함하고 있으며 comment state 변수는 그것의 값을 가지고 있습니다. 어느날, 어떤 프로필에서 다른 프로필로 넘어갈 때 comment state 가 리셋되지 않는 현상이 발생합니다. 결과적으로 잘못된 댓글을 유저의 프로필에 남길 수 있게 될 수 있습니다. 이 이슈를 해결하기 위해 당신은 comment state를 userId 가 바뀔 때마다 리셋하는 코드를 작성한 것입니다.

이것은 비효율적입니다. ProfilePage 와 그것의 자식 컴포넌트들은 이전에 있던 값들과 처음 렌더링되고 그 다음에 다시 렌더링 되기 때문입니다. 또 이것은 복잡합니다. ProfilePage 안에 어떤 state를 가지는 모든 컴포넌트안에서 이런 일을 해줘야 하기 때문입니다. 예를 들어, 댓글 UI가 nested되어 있다면, nested된 댓글 state 또한 리셋해야 할 것이기 때문입니다.

이러한 작업 대신에, 각 유저 프로필에 명백한 키를 제공함으로써 개념적으로 다른 프로필임을 React에 명시해주면 됩니다. 당신의 컴포넌트를 두개로 나누고 key 속성을 전달하는 방식으로요.

export default function ProfilePage({ userId }) {
  return (
    <Profile
      userId={userId}
      key={userId}
    />
  );
}
​
function Profile({ userId }) {
  // ✅ key가 변경될 때마다 자동으로 리셋될 것입니다.
  const [comment, setComment] = useState('');
  // ...
}

보통 React는 같은 컴포넌트가 같은 지점에 렌더링 될 때 state를 보전합니다. Profile 컴포넌트의 기로써 userId를 제공함으로써, 당신은 React에게 서로다른 Profile 컴포넌트에 그 어떤 state도 공유하지 않도록 명령할 수 있는 것입니다. key가 바뀔 때마다, React는 DOM을 새로 만들고 Profile 컴포넌트와 그의 자식 컴포넌트 전부의 상태를 리셋할 것입니다. 이제 comment 필드는 서로 다른 프로필을 이동할 때마다 자동으로 리셋될 것입니다.

 

마치며

지금까지 useEffect 사용을 자제해야 하는 이유와 사용 시 주의해야 할 사항들에 대해 알아보았습니다. 지금까지 state 변경 시 계산되어야 할 것 = useEffect 와 같이 생각하며 코드를 작성해왔었지만, 이제부터는 정말 필요한 useEffect인지, 조금더 가독성 있게 작성할 수 있는 방법은 없는지에 대해 더 고민하면서 코딩 생활을 이어나가야 할 것 같다는 생각이 듭니다.