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
될 때만 수행된다.