

React component가 렌더링 되거나(상태값이 변하거나) 의존성 배열에 존재하는 값의 참조값이 변경될 때
특정 작업(Side Effect)을 실행할 수 있도록 하는 리액트 Hook이다.
Side effect
component 렌더링 된 이후에 비동기로 처리되어야 하는 부수적인 효과
useEffect는 component가mount됐을 때,
component가unmount됐을 때, component가update됐을 때,를 분기하여 특정 작업을 처리할 수 있다.
즉, 클래스형 컴포넌트에서 사용할 수 있었던 생명주기 메소드를
함수형 컴포넌트에서도 사용가능하다.
componentDidMount,componentDidUpdate,componentWillUnmount
useEffect의 반환값은
undefined이다.
component가
mount됐을 때,
component가unmount됐을 때, component가update됐을 때 실행되야할 함수선택적으로
cleanup함수를 작성할 수도 있다.
cleanup함수는setup함수의return문에 작성을 하여 설정할 수 있다.
배열형으로, props, state, 함수 등 표현식이면 값으로 넣을 수 있다.
useEffect setup함수에 작성한 모든 변수와 함수가 포함된다.
만약 lint for React 가 존재한다면 종속성이 올바로 지정되었는지 확인한다.
React는
Object.is()메서드를 사용하여 각 의존성을 이전 값과 비교한다.
Object.is()는 === 과 거의 동일하며 값과 타입이 모두 일치해야한다.만약
dependencies배열이 비어있다면 컴포넌트가 렌더링될때만 useEffect가 실행된다.
React Hook은 컴포넌트 내부의 최상단에서만 사용가능하다.
즉, 반복문이나 조건문 등 다른 블록에 존재해서는 안된다.
외부 시스템과 동기화하는 것이 아니라면useEffect가 꼭 필요하지 않을 수 있으니 고려해야한다.
외부 시스템이란 React로 제어되지 않는 모든 코드를 의미한다.
setInterval(),clearInterval()을 사용한 타이머window.addEventListener(),window.removeEventListener()같은 이벤트 리스너animation.start(),animation.reset()같은 API가 포함된 타사 애니메이션 라이브러리
의존성 배열의 값이, 상태값이 아닌 컴포넌트 내부에 정의된 객체인 경우
useEffect가 필요 이상으로 실행될 위험이 있다.컴포넌트가 렌더링되면 컴포넌트 코드를 처음부터 실행되는데
이때 객체를 새로 만들면서 참조값(주소값)이 변경되게 된다.
이 객체는 의존성 배열에 존재하므로useEffect의setup함수가 다시 실행된다.
이 문제를 해결하려면 불필요한 객체를 의존성 배열에서 제거해야한다.
클릭과 같은 상호작용으로 인해 이펙트가 발생한 것이 아니라면,
React는 이펙트를 실행하기 전에 브라우저가 업데이트된 화면을 먼저 그리도록 할 것이다.
useEffect가 시각적인 작업(예: tooltip 위치 지정)을 하고 있는데
깜박임 등의 지연이 눈에 띄는 경우,useEffect를useLayoutEffect로 바꾸는 것을 권장한다.
useEffect는CSR에서만 작동한다.SSR에서는 작동하지 않는다.
Effects only run on the client. They don’t run during server rendering.
Strict Mode인 경우 React는setup과cleanup함수를 한번 더 수행한다.
이 의도는 두 함수가 올바르게 구현되었는지 확인하는 스트레스 테스트이다.
예시가 없어서 아직 감이 안온다.
컴포넌트가
unmount되지 않았는데도cleanup이 실행되는 경우가 발생하는 경우가 있을 것이다.
cleanup은ummount시 뿐만 아니라 변경된 종속성으로 다시 렌더링하기 전에 실행된다.
또한 개발 시 React는 컴포넌트mount직후에setup+cleanup을 한 번 더 실행한다.즉
setup과cleanup은 다음과 같이 대칭적인 로직이어야 한다.useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.connect(); return () => { connection.disconnect(); }; }, [serverUrl, roomId]);
컴포넌트가 재렌더링되는 과정은 다음과 같다.
현재 컴포넌트unmount-> 새로운 컴포넌트mount컴포넌트가
unmount되면cleanup함수가 수행되는데
이때 사용되는 상태값은setState()로 상태값이 변하기 이전의 값을 사용한다.그리고 새로운 컴포넌트가
mount되면setup함수가 수행되는데
이때 사용되는 상태값은setState()로 상태값이 업데이트된 값을 사용한다.
useEffect(() => {
// ...
}, [a, b]); // Runs again if a or b are different
- 초기 렌더링(페인팅) 이후 실행
- 의존성 배열의 참조값 변경 시 실행
useEffect(() => {
// ...
}, []); // Does not run again (except once in development)
- 초기 렌더링(페인팅) 이후에만 실행
useEffect(() => {
// ...
}, []); // Always runs again
- 컴포넌트의 모든 렌더링(및 재렌더링) 시 실행
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
setCount(count + 1); // You want to increment the counter every second...
}, 1000)
return () => clearInterval(intervalId);
}, [count]); // 🚩 ... but specifying `count` as a dependency always resets the interval.
// ...
}
1초마다 상태값 count 를 1씩 증가시키기 위해 다음과 같은
useEffect를 만들었다.
하지만 이렇게하면 count 값이 변경될 때마다(1초마다)cleanup함수가 수행되므로
좋지 않은 예시이다.따라서 아래와 같이 사용하는 것이 이상적이다.
import { useState, useEffect } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
setCount(c => c + 1); // ✅ Pass a state updater
}, 1000);
return () => clearInterval(intervalId);
}, []); // ✅ Now count is not a dependency
return <h1>{count}</h1>;
}
의존성 배열에서
count를 삭제했고setCount에서 이전 값을 사용하기 위해
콜백함수를 파라미터로 지정했다.이렇게하면
cleanup함수는 위 코드와 달리unmount될 때만 수행된다.