React에서 컴포넌트가 렌더링 된 후 특정 작업을 수행할 수 있도록 도와주는 hook이 있는데, 그것이 바로 'useEffect'이다. useEffect를 사용하여 'side effect'를 관리할 수 있다.
🤔 Side Effect는 또 뭐야?
React 컴포넌트는 순수 함수와 같이 동작해야 한다. 즉, 같은 props를 입력하면 항상 같은 결과를 출력해야 한다. 하지만 이런 규칙을 벗어나는 작업들 예를 들어 네트워크 요청, I/O, DOM 수정 등을 처리해야 하는데 이를 'side effect'라고 칭한다.
useEffect hook은 이러한 side effect를 효과적으로 처리하도록 도와준다. useEffect는 두 가지 주요 케이스에서 유용하다.
- 컴포넌트가 마운트 될 때 (처음 렌더링 될 때)
- 컴포넌트의 특정 props나 상태가 변경될 때
useEffect의 기본 형태는 다음과 같다.
useEffect(() => {
// 실행할 sideEffect
});
useEffect의 첫 번째 인자로 전달되는 함수가 바로 side effect 함수이다. 이 함수는 컴포넌트가 렌더링된 이후에 실행된다.
아래 예제 코드를 통해 useEffect의 기본적인 사용 예시를 알아보자.
import React, { useState, useEffect } from 'react'; // useEffect import
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
위 예제 코드에서는 버튼을 클릭할 때 마다 setCount로 count state가 변경되고, 그로인해 첫 번째 인자로 전달한 함수가 실행되어, 문서의 제목 (document.title)이 변경된다. 이것도 일종의 side effect라고 할 수 있다.
// useEffect의 의존성 배열의 기본 형태
useEffect(() => {
// 실행할 side effect
}, [dependency1, dependency2, ...]);
useEffect의 두 번째 인자로 배열을 전달할 수 있으며, 이를 의존성 배열 이라고 한다.
의존성 배열에는 useEffect의 내부에서 참조하는 상태의 값이나 props를 넣는다. 이 배열의 요소가 변경될 때마다 첫 번째 인자로 넣어준 side Effect 함수가 재실행된다.
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // `count`가 변경될 때마다 effect가 실행된다.
위 코드에서는 'count'가 변경될 때 마다 effect가 실행되게 된다.
만약, 의존성 배열을 빈 배열로 전달할 경우, effect 함수는 컴포넌트가 처음 마운트 될 때만 실행된다.
useEffect(() => {
console.log('This only runs once');
}, []); // 컴포넌트가 처음 마운트 될 때만 실행된다.
또한, 두 번째 인자로 배열을 전달하지 않을 경우 effect 함수는 컴포넌트가 렌더링 될 때 마다 실행된다. 이로인해 잘못 사용할 경우 무한루프에 빠지는 경우가 있으니 주의하도록 하자.
useEffect에서 반환하는 함수는 clean-up 함수로 사용된다. 이 함수는 다음 effect가 실행되기 전이나 컴포넌트가 unmount 될 때 실행된다. clean-up 함수는 주로 이벤트 리스너의 제거, setTimeout이나 setInterval 같은 비동기 작업의 취소 등의 클린업 작업에 사용된다.
// clean-up 함수를 사용하는 useEffect의 기본 형태
useEffect(() => {
// 실행할 side effect
return () => {
// Clean-up 작업
};
});
import React, { useState, useEffect } from 'react';
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const timerID = setTimeout(() => {
setCount((prevCount) => prevCount + 1);
}, 1000);
return () => {
clearTimeout(timerID); // clean-up
};
}, [count]);
return (
<div>
<p>Timer: {count} seconds</p>
</div>
);
}
export default Timer;
위 예제 코드에서는 useEffect 내부에서 setTimeout을 사용하여 count 상태를 1초마다 증가시키는 타이머를 설정하였다. setTimeout은 타이머 ID를 반환하는데, 이는 나중에 타이머를 취소하기 위해 사용된다.
useEffect의 반환 함수(return 부분)에서 clearTimeout을 사용하여 타이머를 정지시키는 clean-up 작업을 수행한다. 이 clean-up 함수는 컴포넌트가 unmount 될 때나 count 상태가 변경될 때마다 호출되어 이전 타이머를 정리하게 된다. 이렇게 함으로써 타이머가 중복으로 실행되거나 컴포넌트가 unmount 된 후에도 계속 실행되는 문제를 방지할 수 있다.
🤔 useEffect를 쓰면 뭐가 좋은거지?
- 렌더링 후에 부수적인 작업을 수행할 수 있다. 이를 통해 API 요청, 타이머 설정 등의 작업을 수행할 수 있다.
- 의존성 배열을 통해 어떤 상태/prop의 변경에 따라 작업을 다시 실행할 수 있다.
- clean-up 함수를 통해 리소스 정리를 수행할 수 있으며, 이를 통해 메모리 누수를 방지할 수 있다.
useEffect는 React에서 비동기 작업을 수행하고, 외부 데이터를 불러오고, 직접 DOM을 조작하는 등의 작업을 하는데 사용된다. 이런 작업들은 모두 컴포넌트의 렌더링이 완료된 후에 수행되어야 하므로 useEffect를 사용하는 것이 적절하다고 할 수 있다.