리액트에서 말하는 라이프사이클이란 개발자가 리액트의 컴포넌트를 보다 정밀하게 통제할 수 있도록 컴포넌트가 처음 생성되고 제거되기까지의 과정을 분기해둔 것입니다.

클래스형 컴포넌트의 생명주기는 위와 같은 형태로 분기되어 제어됩니다. 하지만 함수형 컴포넌트의 경우 메서드들이 제공되지 않고 어느정도 같은 기능을 하는 Hook을 제공합니다.

함수형 컴포넌트는 클래스형 컴포넌트에서 제공하는 라이프사이클 메서드를 직접 사용하는것은 불가능합니다. 하지만 유사한 기능을 구현할 수 있는 useEffect 라는 훅을 제공하는데요.
함수형 컴포넌트는 크게 생성, 갱신, 제거의 라이프사이클을 가집니다.
useEffect는 컴포넌트의 사이드 이펙트를 제어하는 Hook입니다. 즉, 컴포넌트의 라이프사이클을 제어함에 따라 특정 행위를 할 수 있게 해줍니다.

기본적인 형태는 위와 같고 의존성 배열을 통해 컴포넌트의 생성, 갱신, 제거를 통제할 수 있습니다.
의존성 배열은 컴포넌트에서 사용하는 상태(State)나 속성(Props)을 입력하며, 입력된 요소가 변경될 때마다 콜백 함수가 실행되도록 관리합니다.
컴포넌트가 처음 화면에 렌더링되는것을 의미하며, 의존성 배열은 빈 배열로 입력합니다.
useEffect(()=>{ console.log('마운트에만 반응') },[])
🙋🏻♂️ 왜 빈배열일까요?
의존성 배열은 컴포넌트의 상태와 속성에 반응한다고 하였습니다. 즉, 컴포넌트가 리렌더링 될 때를 감지하여 콜백함수를 실행하는것입니다.그렇다면 의존성 배열을 비워둔다면 어떻게 될까요? 감지할 상태와 속성이 없기때문에 리렌더링에 반응하지 않는것입니다.
생성된 컴포넌트가 리렌더링 되는것을 의미하며, 콜백함수와 관련된 상태와 속성을 입력합니다.
useEffect(()=>{ console.log(`${state}변화 감지`) },[state])
🙋🏻♂️ 마운트시에도 실행되는데요?
네 맞습니다. useEffect는 기본적으로 마운트시에 반드시 실행됩니다. 설계가 그렇기 때문인데요. 그렇다면 마운트를 제외하고 업데이트에만 실행하게 하려면 어떻게 해야할까요?
const isMount = useRef(false);
useEffect(()=>{
if(!isMount.current){
isMount.current = true;
return
}
console.log(`${state}변화 감지`)
},[state])
이처럼 useRef를 사용한 플래그를 생성하고 그 플래그를 사용하여 마운트시점을 제외하여 업데이트 사이클에만 반응하게 할 수 있습니다.
❗️useEffct 내부에서 의존성 배열의 요소를 업데이트하지마세요!
useEffect(() => { setCount(count + 1); // 의존성 배열에 count가 포함되면 무한 루프에 빠짐 }, [count]); // 잘못된 사용 예리렌더링의 반복된 발생으로 무한루프에 빠집니다.
생성된 컴포넌트가 화면에서 제거되는것을 의미합니다.
useEffect(()=>{ return ()=>{} },[])
useEffect 콜백함수의 return 은 컴포넌트가 언마운트 될 때 실행되며 함수를 정리한다는 의미에서 클린업 함수라고도 말합니다.
function Even() {
useEffect(() => {
console.log('마운트');
return () => {
console.log("언마운트");
};
}, []);
return <></>;
}
function App() {
const [count, setCount] = useState(0);
return (
<>
{count}
<button
onClick={() => {
setCount((prev) => prev + 1);
}}
>
+
</button>
{count % 2 ? <Even /> : undefined}
</>
);
}
count의 상태에따라 마운트와 언마운트를 반복하는 컴포넌트 Even이 언마운트 될 때 작동하는 것을 확인할 수 있습니다.
🙋🏻♂️ 클린업 함수는 언제써요?
컴포넌트가 언마운트되거나, 의존성 배열 내 값이 변경되어 useEffect가 재실행될 때, 리소스를 정리하거나 부작용(side effect)을 제거하기 위해서입니다. 클린업 함수는 메모리 누수와 같은 문제를 방지하고, 애플리케이션의 성능을 유지하는 데 중요한 역할을 합니다.
이벤트 리스너 정리
useEffect(() => {
function handleResize() {
console.log('Window resized');
}
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
만약 이벤트 리스너를 정리하지 않는다면?
컴포넌트가 언마운트 된 이후에도 이벤트리스너는 계속 작동하여 불필요한 리소스를 낭비하고 복잡한 로직과 Props가 얽혀있다면 에러를 유발합니다.
타이머 정리
useEffect(() => {
const timer = setInterval(() => {
console.log('Timer tick');
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
만약 타이머를 정리하지 않는다면?
컴포넌트가 언마운트 된 이후에도 타이머는 계속 스케쥴링 되어 리소스를 낭비하고 복잡한 로직과 Props가 얽혀있다면 에러를 유발합니다.
추가적으로 uesEffect 사용 방법에 관한 고찰에대한 좋은 블로그가있어서 추천드립니다.