프론트엔드 공부하는 친구들과 얘기하다 보면 너무도 자주 사용하는 useEffect에 대해서 종종 오해하고 있는 경우가 있다고 생각했다. 그래서 한 번 정리해봤다.
useEffect는 리액트 컴포넌트가 렌더링될 때마다 특정 작업을 수행하도록 설정할 수 있는 Hook이다.
useEffect(setup, dependencies?)
useEffect에서 설정한 함수를 컴포넌트가 맨 처음 렌더링될 때만 실행하고, 업데이트될 때는 실행하지 않으려면 함수의 두 번째 파라미터로 빈 배열을 넣어주면 된다.
useEffect(() => {
console.log("마운트 될 때만 실행됩니다.");
}, []);
useEffect(() => {
console.log(name);
}, [name]);
useEffect의 두 번째 파라미터로 전달되는 배열 안에 검사하고 싶은 값(의존 값)을 넣어 주면 된다. 빈 배열이나 의존 값이 들어있는 배열을 거의 넣고, 배열을 아예 생략하는 상황은 드물다.
useEffect는 기본적으로 렌더링되고 난 직후마다 실행되며, 두 번쨰 파라미터 배열에 무엇을 넣는지에 따라 실행되는 조건이 다르다.
컴포넌트가 언마운트되기 전이나 업데이트되기 직전에 어떠한 작업을 수행하고 싶다면 useEffect에서 뒷정리(cleanup) 함수를 반환해 주어야 한다.
useEffect(() => {
console.log("effect");
consoel.log(name);
return () => {
console.log("cleanup");
console.log(name);
};
});
위 코드의 콘솔로그 순서는
effect
name
effect
name
cleanup
name
effect
name
cleanup
name
effect
name
cleanup
name
cleanup
name
위 출력은 모두 개발 환경에서 두 번씩 찍힌 예시이고 아래 오해, 사용법에 이유를 적어놨다.
useEffect(() => {
console.log("effect");
return () => {
console.log("cleanup");
};
}, []);
오직 언마운트될 때만 뒷정리 함수를 호출하고 싶다면 useEffect 함수의 두 번째 파라미터에 비어있는 배열을 넣으면 된다.
마운트
: 컴포넌트가 처음 DOM에 삽입되는 단계.
함수형 컴포넌트에서의 예시
useEffect(() => {
// 마운트 시 실행될 코드
}, []);
업데이트
: 컴포넌트가 다시 렌더링될 때 발생하는 단계. 주로 props나 state 변화에 의해 트리거된다.
함수형 컴포넌트
useEffect(() => {
// 특정 값이 변경될 때 실행될 코드
}, [특정 값]);
언마운트
: 컴포넌트가 DOM에서 제거되는 단계.
useEffect(() => {
return () => {
// 언마운트 시 실행될 코드
};
}, []);
클래스형 컴포넌트의 생명주기 메서드는 다르지만 여기서는 다루지 않겠다.
useEffect는 컴포넌트의 최상위 수준이나 자체 Hook에서만 호출할 수 있다. 루프나 조건 내에서는 호출할 수 없다.
React.StrictMode 모드에서 2번 실행
React.StrictMode가 적용된 개발 환경이면 useEffect를 사용한 코드에 문제가 있는지 없는지를 감지하기 위하여 두 번 실행이 된다.
이는 미래의 리액트에서는 컴포넌트가 사라졌다가 다시 나타나도 컴포넌트의 상태를 유지하는 기능을 도입할 예정인데, 컴포넌트가 나타날 때 useEffect가 두 번 실행이 되어도 컴포넌트 작동 방식에 문제가 없어야 추후 호환이 정상적으로 이뤄지기 때문이다. 이는 개발환경에서만 일어나는 현상이다.
-> 이를 모르면 오해를 하기 쉬운데 useEffect 2번 작동이 문제가 있다거나, strict 모드를 없애야 한다 등 ..
하지만 위에 언급한 것처럼 이는 미래의 작동을 위해 일부러 2번이 작동하게 하는 스트레스 테스트일 뿐이다. 2번 실행을 하고 싶지 않다면 React.StrictMode를 쓰지 않거나 useEffect를 쓰지 않는 방법이 있다.
정리하면
useEffect가 2번 작동하는건 잘못된게 아니다. 일부러 미래의 작동 방식을 위해 2번 작동하게 만들어둔거다.
strictMode일 때, 개발 환경일 때만 2번 작동한다.
나는 프로젝트에서 로그인 부분을 구현시 토큰 요청을 useEffect로 보낼때 백엔드 로직에서 2번 요청을 연속적으로 받으면 종종 토큰이 제대로 인식이 안되는 문제가 있어서 react query 요청으로 바꿔 해결한 경험이 있다. 👉 해당 글
React.StrictMode를 안쓰고 싶다면 다음과 같은 StrictMode가 있는 이유를 인식하고, 이러한 기능이 필요없다면 없애면 된다.
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);
React.StrictMode
리액트 애플리케이션에서 잠재적인 문제를 감지하고, 안정성과 최적화를 위해 일부 기능을 활성화하는 개발 도구이다. 컴포넌트 렌더링이나 프로덕션 빌드에 영향을 미치지 않는다. 개발 중에 문제를 감지해 경고를 출력한다.주요 기능
- 중복 함수 호출: React.StrictMode는 개발 모드에서 일부 함수 컴포넌트의 효과와 라이프사이클 메서드를 두 번 호출한다. 이는 순수 함수의 순수성, 부작용 검사, 버그 조기 발견을 돕는다.
- 안전한 라이프사이클 메서드 사용: 사용하지 않는 또는 권장되지 않는 라이프사이클 메서드 사용에 대한 경고를 출력한다.
- 경고 활성화: 잠재적인 문제(예: 레거시 문자열 ref 사용, UNSAFE_로 시작하는 메서드 사용 등)에 대한 경고를 출력한다.
- 레거시 API 확인: StrictMode는 레거시 API의 사용을 감지하고 경고한다. 이는 향후 리액트 버전에서 호환되지 않을 수 있다.
- 불변성 확인: React가 의도한 방식대로 상태와 props를 불변하게 처리하지 않을 때 경고합니다.
개발시 안정성과 성능 최적화, 미래 호환 확인을 해주는 도구이다.
위와 같은 장점으로 StrictMode를 빼고 개발한 적은 없고, 이유가 없다면 굳이 안쓸 이유는 없다고 생각한다.
useEffect는 빈 배열을 명시하고 렌더링할 때마다 실행해야 하는 특정 로직이 있을때 주로 사용하는데 가끔 useEffect가 필요없이 한 번만 불러오면 되는 코드에도 쓰는걸 발견할 때가 있다. 동작 원리를 이해하고 불필요한 상황 예를 들어 컴포넌트가 마운트될 때 한 번만 실행되어야 하는 로직에서는 사용을 피하자.
김민준, 『리액트를 다루는 기술(개정판)』, 길벗(2023)