리액트 개발자라면 `useEffect` 훅의 강력함을 알고 있지만, 이를 남용하면 애플리케이션의 성능 저하와 복잡성을 초래할 수 있습니다. 이번 글에서는 `useEffect`의 올바른 사용법과 남용을 피하는 방법을 상세히 분석하고, 불필요한 리렌더링을 최소화하는 전략을 소개합니다.
리액트의 상태 관리와 `useEffect`의 역할
프런트엔드 개발은 상태 관리, 반응성 보장, 성능 최적화 등 다양한 복잡한 과제를 포함합니다. 리액트는 이러한 문제를 해결하기 위해 `useEffect`와 같은 훅을 제공하지만, 잘못 사용하면 오히려 문제를 악화시킬 수 있습니다.
`useEffect`의 기본 사용법
`useEffect`는 컴포넌트가 렌더링된 후 특정 작업을 수행할 수 있게 해주는 훅입니다. 주로 데이터 fetching, 구독 설정, DOM 업데이트 등에 사용됩니다.
useEffect(() => {
// 실행할 코드
}, [dependencies]);
`useEffect` 남용의 위험성
`useEffect`를 남용하면 다음과 같은 문제가 발생할 수 있습니다:
- 불필요한 리렌더링: 잘못된 의존성 배열 설정으로 인해 컴포넌트가 불필요하게 자주 렌더링될 수 있습니다.
- 경쟁 상태(race condition): 비동기 작업 중 마지막 요청이 아닌 이전 요청의 결과가 상태를 업데이트할 수 있습니다.
- 코드 복잡성 증가: 클린업 함수나 추가 상태 관리를 통해 코드가 복잡해질 수 있습니다.
경쟁 상태 예제
잘못된 `useEffect` 사용으로 인해 발생하는 경쟁 상태의 예를 살펴보겠습니다.
function RaceConditionExample() {
const [counter, setCounter] = useState(0);
const [response, setResponse] = useState(0);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const request = async (requestId) => {
setIsLoading(true);
await sleep(Math.random() * 3000);
setResponse(requestId);
setIsLoading(false);
};
request(counter);
}, [counter]);
const handleClick = () => {
setCounter(prev => ++prev);
};
return (
<>
<button onClick={handleClick}>Increment</button>
{/* ... */}
</>
);
}
이 코드는 예상과 다르게 이전 요청이 응답을 덮어쓰는 문제가 발생할 수 있습니다.
클린업 함수로 경쟁 상태 해결
리액트의 새로운 문서에 따르면 클린업 함수를 사용하여 경쟁 상태를 해결할 수 있습니다.
useEffect(() => {
let ignore = false;
const request = async (requestId) => {
setIsLoading(true);
await sleep(Math.random() * 3000);
if (!ignore) {
setResponse(requestId);
setIsLoading(false);
}
};
request(counter);
return () => {
ignore = true;
};
}, [counter]);
이렇게 하면 마지막 요청의 응답만 상태를 업데이트하게 되어 경쟁 상태를 방지할 수 있습니다.
`useEffect` 올바르게 사용하는 법
불필요한 상태 업데이트 피하기
`useEffect` 내부에서 상태를 업데이트하면 추가적인 리렌더링이 발생할 수 있습니다. 가능한 경우, 렌더링 중에 계산을 수행하여 상태 업데이트를 최소화하세요.
데이터 흐름의 명확성 유지
데이터는 부모에서 자식으로 흐르는 방향으로 관리하여 코드의 가독성과 유지보수성을 높이세요. 불필요한 콜백을 사용하지 말고, 상태를 끌어올려 관리하는 것이 좋습니다.
한 번만 실행해야 하는 로직 처리
앱 초기화와 같이 런타임 중 한 번만 실행되어야 하는 로직은 `useEffect` 대신 다른 방법을 고려하세요. 예를 들어, 최상위 플래그를 사용하거나 렌더링 전에 로직을 실행할 수 있습니다.
`useEffect` 사용 시 주의사항
- 클린업 함수 사용: 비동기 작업이나 구독 설정 시 클린업 함수를 통해 리소스를 정리하세요.
- 의존성 배열 정확히 설정: 필요한 의존성만 포함하여 불필요한 리렌더링을 방지하세요.
- 로컬 상태 최소화: 가능한 한 상태 관리는 렌더링 중에 수행하고, `useEffect` 내부에서 상태를 업데이트하지 마세요.
결론
`useEffect`는 리액트에서 강력한 도구이지만, 이를 올바르게 사용하지 않으면 애플리케이션의 성능과 유지보수성에 부정적인 영향을 미칠 수 있습니다. 본 가이드에서 제시한 최적화 전략을 통해 `useEffect`를 효과적으로 활용하여 안정적이고 성능 좋은 리액트 애플리케이션을 개발하시기 바랍니다.
참고 자료: Aviv Segal, “Leave useEffect Alone!”