useEffect는 외부 시스템*과 컴포넌트를 동기화하는 React Hook입니다.
몇몇 컴포넌트들은 페이지에 표시되는 동안 네트워크나 브라우저 API, 또는 서드파티 라이브러리와의 연결이 유지되어야 합니다. React에 제어되지 않는 이러한 시스템들을 외부 시스템(external)* 이라 부릅니다.
위와 같이 리액트 공식문서에 useEffect에 대한 설명이 나와있습니다. 제가 기존에 알고 있던 useEffect의 정의는 렌더링 이후에 side effect를 처리하기 위해 사용되는 훅이라고 알고 있습니다.
여기서 말하는 사이드 이펙트(sde effect)란 렌더링 자체에 의해 발생하는 부수 효과를 특정하는 것으로, 특정 이벤트가 아닌 렌더링에 의해 직접 발생합니다.
// 1️⃣ 렌더링 이후에 DOM 조작
import { useEffect, useRef } from 'react';
function VideoPlayer({ src, isPlaying }) {
const ref = useRef(null);
useEffect(() => {
if (isPlaying) {
ref.current.play();
} else {
ref.current.pause();
}
}, [isPlaying]);
return <video ref={ref} src={src} loop playsInline />;
}
// 2️⃣ 서버와의 연결 (위 예시와 동일)
useEffect(() => {
const connection = createConnection();
connection.connect();
// 클린업 함수 지정
return () => {
connection.disconnect();
};
}, []);
// 3️⃣ 이벤트 리스너를 등록하거나 해제할 경우
useEffect(() => {
function handleScroll(e) {
console.log(window.scrollX, window.scrollY);
}
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
// 4️⃣ 데이터 패칭
useEffect(() => {
let ignore = false;
async function startFetching() {
const json = await fetchTodos(userId);
if (!ignore) {
setTodos(json);
}
}
startFetching();
return () => {
ignore = true;
};
}, [userId]);
개발 환경에서 Strict Mode가 활성화되면 React는 실제 설정 이전에 설정과 정리를 한번 더 실행합니다.
그렇다면 StrictMode를 끄는 게 좋은 방법일까요?
=> Strict Mode를 사용하지 않는 것은 좋은 선택이 아닙니다.
Strict Mode를 없애 두번씩 렌더링하는 과정이 일어나지 않게 할 수 있지만 production 환경에서 일어날 수 있는 오류를 리엑트에서 잡아주지 못하므로 항상 Strict Mode에서 개발하는 것이 좋습니다.
Effect가 두 번 일어나도 유저가 이를 느끼지 못하게 코드를 작성해야 합니다.
=> 해결 방법은 클린업 함수를 꼭 작성하는 것입니다.
useEffect(() => {
function handleScroll(e) {
console.log(e.clientX, e.clientY);
}
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
useEffect 내부에서 상태를 변경하는 로직이 포함되어 있다면, 그 변경된 상태에 의해 컴포넌트가 다시 렌더링되고, 이는 또 다른 useEffect의 호출을 야기할 수 있기 때문입니다. 이러한 반복은 성능 저하를 일으키거나 예상치 못한 버그를 발생시킬 수 있습니다.
그래서 우리는 useEffect 안에서 props나 state를 조작하는 것이나 의존성 배열 안에 리렌더링을 유발할 수 있는 변수를 담는 걸 삼가는 게 좋고, 외부 시스템을 위한 useEffect를 썼는지 되새김하면 좋습니다.
메모리 누수를 방지하기 위해, useEffect 내에서 시작된 모든 작업은 컴포넌트가 언마운트 될 때 정리되어야 합니다. 정리 로직은 설정 로직과 ‘대칭’이어야 하며 설정이 수행한 것을 중지하거나 되돌릴 수 있어야 합니다.
// 2️⃣ 서버와의 연결
useEffect(() => {
const connection = createConnection();
connection.connect();
// 클린업 함수 지정
return () => {
connection.disconnect();
};
}, []);
Effect 안에서 직접 가져오는 것은 일반적으로 데이터를 미리 로드하거나 캐시하지 않음을 의미합니다.
useEffect로 데이터 패칭을 하면 렌더링 이후 데이터 패칭을 하여 네트워크 폭포를, 미리 로드하거나 캐싱할 수 없어서 성능 저하를 야기할 수 있습니다.
그래서 서버 상태를 관리하는 React-Query로 데이터 패칭과 캐싱 전략을 사용하면 성능을 개선할 수 있습니다.
참고 링크