먼저, 다음과 같은 코드를 보시죠
useEffect(() => {;
console.log('useEffect 실행');
}, []);
이 코드를 실행하면 useEffect가 한 번만 실행될 것으로 예상하게 됩니다.
하지만 실제로 콘솔을 확인해보면, useEffect 실행
이 두 번 출력되는 것을 볼 수 있습니다. 왜 이런 일이 발생하는 걸까요? 🤷♂️
이 현상은 React 18에서 도입된 Strict Mode 때문에 발생합니다.
React 공식 문서에서는 Strict Mode에 대해 다음과 같이 설명하고 있습니다.
"
<StrictMode>
를 사용하면 개발 초기에 구성 요소의 일반적인 버그를 찾을 수 있습니다."
"Strict Mode 검사는 개발 중에만 실행되지만, 코드에 이미 존재하지만 프로덕션에서 안정적으로 재현하기 어려울 수 있는 버그를 찾는 데 도움이 됩니다.
Strict Mode를 사용하면 사용자가 버그를 신고하기 전에 버그를 수정할 수 있습니다."
참고 : https://react.dev/reference/react/StrictMode
그래서 useEffect가 두번 실행되는 것을 어떻게 막을 수 있을까요? 💡
Strict Mode를 제거하면 문제를 해결할 수 있지만, 잠재적 버그를 놓칠 수 있어 권장되지 않습니다.
간단한 콘솔 출력이나 상태 설정의 경우 두 번 실행되어도 큰 문제가 없습니다.
하지만 API 호출이나 반드시 한 번만 실행되어야 하는 동작이 두 번 실행되면 문제가 될 수 있습니다.
이를 해결하기 위해, useEffect의 클린업 함수에서 이전에 설정된 값을 초기화해야 합니다. 특히 API 요청의 경우, 중복 요청으로 인한 문제를 방지하기 위해 이전 요청을 적절히 처리해야 합니다.
useEffect가 두번 실행되는 것을 막기 위한 방법 중 하나는 AbortController를 사용하는 것입니다.
function Test() {
useEffect(() => {
const controller = new AbortController();
console.log('doSomethingAsync: ');
doSomethingAsync(
{ value: 'test' },
{ signal: controller.signal }
).then((result) => {
console.log('result: ', result);
});
return () => {
controller.abort();
};
}, []);
return <div></div>;
}
function doSomethingAsync(payload: unknown, { signal }: { signal?: AbortSignal } = {}): Promise<unknown> {
if (signal?.aborted) {
return Promise.reject(new AbortException());
}
return new Promise((resolve, reject) => {
const abortHandler = () => {
reject(new AbortException());
};
setTimeout(() => {
signal?.removeEventListener('abort', abortHandler);
resolve(payload);
}, 0);
signal?.addEventListener('abort', abortHandler);
});
}
class AbortException extends DOMException {
constructor() {
super('Aborted', 'AbortError');
}
}
이 방법을 사용하면 useEffect는 두 번 실행되지만, 중요한 비동기 작업은 한 번만 완료합니다.
![[Pasted image 20240828124528.png]]
이 방법이 효과적인 이유는 다음과 같습니다.
이렇게 하면 useEffect 자체는 두 번 실행되지만, doSomethingAsync가 취소되지 않고 실행되는 것은 단 한 번입니다.
다만, useEffect를 두번 실행하지 않기위해 사용하기에는 과도한 개발인것 같아요. 🤔
function useEffectOnce(callback: () => void) {
const ref = useRef(false);
useEffect(() => {
if (ref.current) return;
ref.current = true;
if (typeof callback === 'function') {
callback();
}
}, []);
}
이 방법이 효과적인 이유는 다음과 같습니다.
이렇게 하면 useEffect는 여전히 Strict Mode에서 두 번 실행되지만, 우리가 원하는 로직은 딱 한 번만 실행되는 거죠!
function MyComponent() {
useEffectOnce(() => {
console.log('이 메시지는 단 한 번만 출력됩니다!');
// 여기에 초기 데이터 로딩이나 이벤트 리스너 등록 같은 작업을 넣으면 됩니다.
});
return <div>My Component</div>;
}
useEffectOnce는 AbortController를 사용한 방법에 비해 더 간단하고 직관적이에요.
다만, 모든 상황에 이 훅을 사용하는 것은 주의해야 해야합니다. 때로는 useEffect의 재실행이 필요한 경우도 있을테니까요.
긴 글을 읽어주셔서 감사합니다.
추가적인 질문이나 의견이 있다면 언제든 공유해 주세요! 💬🚀
참고 : https://medium.com/@tangiblej/cancel-a-promise-inside-react-useeffect-12a101606b72