Jiseong's Web Dev blog
useEffect 남용을 줄이자. 본문
useEffect 남용의 문제
React는 useEffect를 남용하게 쉽게 만들어뒀다는 생각이 들었다.
일반적으로 잘 못 사용하는 패턴이다.
- react의 상태에 반응해서 실행시키고싶다.
- 서로다른 내부 상태를 동기화 하고싶다.
왜 잘 못 되었는지, 왜 사용하면 안 되는지 이해해보자.
🙃 useEffect란?
위 문제를 해결하기 위해서는 근본적으로 useEffect가 무엇인지 알아야한다.
React useEffect
useEffect는 외부 시스템과 컴포넌트를 동기화하는 React Hook입니다.
공식 문서에 따르면 외부 시스템과 연동하는 용도라고 첫 줄에 나와있다.
예를 들면
- 소켓 연결
- DOM 제어
- 전역 이벤트 리스너 등록/해제
- 서드파티 라이브러리와 연동
등이 있다.
여기서 DOM 제어는 한 번 더 생각해야할 요소이다.
가능하면 react 상태를 통해 관리하는게 좋다.
React useRef
Ref는 일종의 탈출구입니다. 다른 컴포넌트의 DOM 노드를 수동으로 조작하면 코드를 불안정하게 만들 수 있습니다.
useEffect 잘못 된 사용과 문제점
내부 state를 서로 동기화하기 위한 useEffect
문제
function Example() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
useEffect(() => {
setCount2(count);
}, [count]);
return <button onclick={() => setCount1(cur => cur+1)}/>
}
count1과 count2는 같은 값을 가지도록 useEffect를 사용했다.
문제점 1. 불필요한 리렌더링이 일어난다.count가 업데이트 -> 리렌더링 -> count2 업데이트 -> 리렌더링
리액트의 배치처리를 활용 못하게되어 리렌더링이 2회로 늘어난다.
지금은 간단한 예시지만, 이러한 패턴이 여러 개 쌓이게된다면 디버깅이 매우 어려워지고, 복잡한 로직이 된다.
문제점 2. 항상 같은 값이 아닐 수도있다.
count1가 업데이트 된 이후 count2가 업데이트되는 순서가 생기기때문에, 항상 같은 값이 아닐 수 있다.
해결
function Example() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const setCount = (value: number) => {
setCount1(value);
setCount2(value);
}
return <button onclick={() => setCount(count1 + 1)}/>
}
function Example() {
const [count1, setCount1] = useState(0);
const count2 = count1
return <button onclick={() => setCount1(cur => cur+1)}/>
}
이 말고도 다양한 방법이 있을 것이다.
배치처리를 사용하는 방법, 상태 선언을 아예 지우는 방법 등 상황에 맞게 사용하자.
첫번 째 예시의 경우 React가 자동으로 배치처리하기 때문에 count1, count2상태는 동시에 업데이트된다.
따라서 리렌더링이 1회만 발생한다.
마운트 시 1회만 실행하기
function Example() {
useEffect(() => {
fetch('http://example.com');
}, []);
return <></>
}
네트워크 통신은 외부시스템과 연동이기 때문에 올바른 사용법이다.
만약, 해당 로직에 상태가 포함되어야한다면 아래처럼 의존성 배열이 추가될 것이다.
function Example() {
const [page, setPage] = useState(0);
useEffect(() => {
fetch(`http://example.com?page=${page}`);
}, [page]);
return <button onclick={setCount}/>
}
1회만 실행 시키고싶다고해서 page를 의존성 배열에서 지우는것 보다, 리액트 라이프사이클에서 벗어나도록 컴포넌트 외부에서 let 변수를 선언하는것이 올바르다.
let isFetched = false;
function Example() {
const [page, setPage] = useState(0);
// ✅
useEffect(() => {
if(!isFetched){
fetch(`http://example.com?page=${page}`).then(() => isFetched = true);
}
}, [page]);
// ❌
// useEffect(() => {
// fetch(`http://example.com?page=${page}`);
// }, []);
return <></>
}
의존성 배열에 정확히 값을 명시하지 않으면 잠재적인 에러를 유발할 수 있다.
지금은 간단한 예시이지만, 상태를 여러가지 쓴다면 복잡해진다.
react-hooks/exhaustive-deps 룰을 위반 했다는 경고가 발생할 것이다.
최신 상태가 아닌 오래된 상태 값을 사용하기 때문에 발생하는 경고이다.
이를 해결하기 위해 강제로 무시하도록 주석을 달게 될 것이다.
지금 당장 동작한다고해도 다른 개발자들이 본다면 lint룰을 지키기위해 리팩토링할거다.
툭 건들면 터지게 되는 코드가 될 것이다.
이외에도
이외에도 많은 상황에서 useEffect를 제거할 수 있다.
'React' 카테고리의 다른 글
| React Server Component 깊게 이해하기 (0) | 2026.03.15 |
|---|---|
| RadixUI의 asChild, BaseUI의 render (0) | 2026.02.07 |
| 선언적으로 에러처리 아주 깔끔하게 하기 (Next.js 15) (0) | 2024.10.29 |
| 웹뷰에서 소셜 로그인시 문제 해결하기 (2) | 2024.09.15 |
| optimistic update를 사용하면서 고민했던 상황 (0) | 2024.06.09 |