

사람의 인생처럼 리액트 컴포넌트도 태어나고 사라지는 생애주기가 있습니다. 이를 다른 말로 라이프 사이클이라고 합니다.
리액트 컴포넌트의 라이프 사이클은 크게 마운트, 업데이트, 언마운트 총 3단계로 구분합니다.
라이프 사이클을 이용하면 각 단계에 맞게 여러 유용한 작업을 할 수 있습니다.
이를 라이프 사이클 제어 (Lifecylcle Control)라고 합니다. 리액트 훅의 하나인 함수 useEffect를 이용하면 이 사이클을 쉽게 제어할 수 있습니다.

컴포넌트를 페이지에 처음 렌더링할 때
→ 백엔드 서버에 네트워크 요청을 보내서 데이터를 불러오는 기능

State나 Props의 값이 바뀌거나 부모 컴포넌트가 리렌더해 자신도 리렌더될 때
→ 변경된 값을 콘솔에 출력

더 이상 페이지에 컴포넌트를 렌더링하지 않을 때
→ 컴포넌트가 사용하던 메모리 정리

useEffect : 리액트 컴포넌트의 사이드 이펙트(부수적인 효과)를 제어하는 새로운 React Hook
컴포넌트에 변경 사항이 생겼을 때 특정한 코드를 실행시키거나, 라이프 사이클을 제어할 수 있습니다.
import { useState, useEffect } from "react";
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("count 업데이트: ", count);
}, [count]);
const onClickBtn = () = {
setCount(count + 1);
}
return (
<button onClick = {() => onClickBtn()}>버튼</>
);
};
위처럼, useEffect는 두 번째 인수인 count 값이 변경될 때마다 첫 번째 콜백함수 console.log 가 실행시킵니다.
useEffect라는 훅은 두 번째 인수로 전달한 배열에 의존하여 동작하고, 그 이유로 해당 배열을 의존성 배열, 영어로는 depth (dependency array) 라고 부릅니다. depth에는 복수의 배열이 들어갈 수도 있습니다.
그렇다면, 아래처럼 단순히 이벤트 핸들러에 console.log 해도 되는 거 아닌가요?
function App() { const [count, setCount] = useState(0); const onClickBtn = () = { setCount(count + 1); console.log(count); } return ( <button onClick = {() => onClickBtn()}>버튼</> ); };아쉽게도, 제대로 된 값이 출력되지 않습니다. 기대하는 바로는 원래
count의 값이 0이고,setCount함수를 실행하여 1이 되고 콘솔에 1이 나와야 하겠지만, 실제로는 0이 출력됩니다. 여러 번 눌러봐도 실행되기 전 숫자 (기대보다 1 작은 수) 가 출력됩니다. 그 이유는setCount가 비동기 함수이기 때문에, 호출이 된 것이지 아직 실행완료가 되기 전이기 때문에 그렇습니다. 결국 함수를 실행하고 나오는 결과값을 바로 사용하고 싶을 때는useEffect를 써야 합니다.
import { useState, useEffect } from "react";
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("mount");
}, []);
(...)
};
컴포넌트가 마운트 되었을 때 실행시키고 싶은 코드가 있다면, useEffect를 호출하고 depth로는 빈 배열을 전달해주면 됩니다. 이 함수는 처음 마운트될 때 한 번만 실행되고 이 후 다시는 실행되지 않습니다.
import { useState, useEffect } from "react";
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("mount");
}, []); //마운트
useEffect(() => {console.log("update")}); //업데이트
(...)
};
컴포넌트가 업데이트 되었을 때 실행시키고 싶은 코드가 있다면, useEffect를 호출하고 depth 칸을 비워 놓습니다. 이렇게 되면 마운트 시점에 mount와 update가 함께 출력된 후, 리렌더링 될 때마다 update가 실행되게 만들 수 있습니다.
그런데 만약 마운트 시점에 update를 출력하지 않고 오로지 '업데이트' 된 순간에만 출력하고 싶다면 다음과 같이 코드를 추가합니다.
import { useState, useEffect } from "react";
function App() {
const [count, setCount] = useState(0);
const isMount = useRef(false); //아직 마운트 x
useEffect(() => {
if(!isMount.current){ //아직 마운트 x
isMount.current = true;
return; //강제 종료
}
console.log("update");
});
(...)
};
리액트 컴포넌트의 언마운트 시점을 제어하기 위해서는 먼저 클린업(Cleanup) 기능을 이해해야 합니다. 클린업이란 원래 ‘청소’라는 뜻입니다. 프로그래밍에서 이 개념은 특정 함수가 실행되고 종료된 후에, 미처 정리하지 못한 사항을 처리하는 일입니다.
먼저 App 컴포넌트에서 다음과 같이 함수 useEffect를 한 번 더 호출합니다.
(...)
function App() {
(...)
useEffect(() => { ①
setInterval(() => { ②
console.log("깜빡");
}, 1000);
});
(...)
}
저장한 다음, 개발자 도구의 콘솔을 확인하면 1초마다 문자열 깜빡이 출력되는 걸 볼 수 있습니다.

그런데 이때, 여러 가지 숫자 버튼을 연속적으로 클릭하게 되면 '깜빡' 문자열은 1초에 한번씩이 아닌 매우 빠른 속도로 클릭할 때마다 출력되는 현상을 볼 수 있습니다.
그 이유는 두 가지 정도가 있습니다.
우선 클릭할 때마다 App 컴포넌트가 리렌더링되기 때문에 useEffect의 콜백 함수가 새로운 setInterval 함수를 만들고 새 인터벌 간격을 생성하기 때문입니다.
다른 하나는, setInterval을 생성한 후 종료하지 않아 다음 함수랑 얽히게 되기 때문입니다.
따라서 다음 버튼을 눌렀을 때 앞에 만들었던 함수를 종료하는 기능이 필요합니다. 이럴 때 요긴하게 사용하는 기능이 useEffect의 클린업 기능입니다.
(...)
function App() {
(...)
useEffect(() => {
const intervalID = setInterval(() => {
console.log("깜빡");
}, 1000);
return () => {
console.log("클린업");
clearInterval(intervalID);
};
});
(...)
}
살펴보면, useEffect 함수 내부에서 return문을 다시 반환하고 있습니다. 이 함수는 클린업 함수로 useEffect의 콜백 함수가 실행되기 전에 실행됩니다. 클린업 함수는 clearInterval 함수를 호출해 이전에 생성했던 인터벌 함수를 삭제하므로, 결국 컴포넌트를 렌더링할 때 마다 새 인터벌을 생성하고 기존 인터벌은 삭제합니다.
클린업 기능을 이용해 컴포넌트 언마운트에 대해 살펴보겠습니다.
버튼을 눌렀을 때 현재 값이 짝수면 짝수입니다를 화면에 렌더링하고, 홀수가 되면 삭제하는 함수를 추가로 작성합니다.
function Even() {
useEffect(() => {
return () => {
console.log("Even 컴포넌트 언마운트");
};
}, []);
return <div>현재 카운트는 짝수입니다</div>;
}
function App() {
(...)
return (
(...)
{count % 2 === 0 && <Even />}
(...)
위처럼 count 값이 짝수일때만 Even 함수를 실행시키는데, 그 안에 return문을 또 넣어 클린업 함수를 생성합니다. 이렇게 되면 Even 함수가 마운트 될 때 콜백 함수가 실행되고, 마침내 이 함수가 종료될 때 return 문을 반환하여 그의 콜백함수인 클린업 함수가 실행됩니다.

State 변수 count의 초깃값은 0(짝수)이므로 App의 마운트 시점에 Even 컴포넌트 역시 마운트됩니다. 이 상태에서 <+1> 버튼을 클릭하면 State 값이 1(홀수)로 변경됩니다. 따라서 Even 컴포넌트를 언마운트하면서 콘솔에 Even 컴포넌트 언마운트라는 문자열을 출력합니다.