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