useEffect
useEffect는 React에서 컴포넌트의 생명주기를 다룰 수 있는 Hook으로, 사이드 이펙트(Side Effects)를 처리하는 역할을 합니다.
1. useEffect의 역할
비동기 API 요청 (데이터 패칭)
이벤트 리스너 등록 및 해제
DOM 조작 (클래스 추가, 애니메이션 등)
타이머 설정 및 정리 (setTimeout, setInterval)
컴포넌트가 마운트/언마운트될 때 특정 로직 실행
2. useEffect 기본 문법
1) 기본적인 사용 (마운트 시 실행)
import { useEffect } from "react";
const Example = () => {
useEffect(() => {
console.log("✅ 컴포넌트가 마운트됨!");
}, []); // 빈 배열 [] → 마운트 시 한 번 실행
return <h1>안녕하세요!</h1>;
};
export default Example;
[](빈 배열)를 두 번째 인자로 넘기면, useEffect는 마운트 시에 한 번만 실행됨.
렌더링될 때마다 실행되지 않음.
2) 특정 상태 변경 시 실행 (의존성 배열)
import { useEffect, useState } from "react";
const Example = () => {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`🌀 count가 변경됨: ${count}`);
}, [count]); // count가 변경될 때만 실행
return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>증가</button>
</div>
);
};
export default Example;
의존성 배열 [count]를 넣으면, count가 변경될 때만 실행됨.
useEffect 내부의 console.log()는 count가 변경될 때만 실행됨.
3) return을 사용한 클린업 (컴포넌트 언마운트 시 실행)
import { useEffect, useState } from "react";
const Example = () => {
const [visible, setVisible] = useState(true);
return (
<div>
{visible && <Timer />}
<button onClick={() => setVisible(!visible)}>토글</button>
</div>
);
};
const Timer = () => {
useEffect(() => {
const interval = setInterval(() => {
console.log("⏰ 타이머 실행 중...");
}, 1000);
return () => {
clearInterval(interval);
console.log("❌ 타이머 정리됨!");
};
}, []);
return <h1>타이머가 실행 중입니다!</h1>;
};
export default Example;
컴포넌트가 언마운트될 때 return 내부의 함수가 실행됨.
타이머(setInterval)를 정리하여 메모리 누수를 방지함.
4) useEffect에서 API 요청 (비동기 요청)
import { useEffect, useState } from "react";
const Example = () => {
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/todos/1");
const result = await response.json();
setData(result);
};
fetchData();
}, []); // 마운트 시 한 번만 실행
return (
<div>
{data ? <h1>{data.title}</h1> : <p>로딩 중...</p>}
</div>
);
};
export default Example;
컴포넌트가 마운트될 때 fetchData() 함수가 실행됨.
비동기 데이터가 도착하면 setData(result)를 통해 화면을 업데이트함.
[]을 넣어 한 번만 실행되도록 설정함.
3. useEffect의 실행 시점
| 컴포넌트가 마운트될 때 ([] 사용) | 실행됨 |
| 컴포넌트가 리렌더링될 때 | 기본적으로 실행 안 됨 |
| 특정 상태(count 등)가 변경될 때 ([count] 사용) | 실행됨 |
| 컴포넌트가 언마운트될 때 (return 사용) | 실행됨 |
즉, useEffect는 특정 조건에서만 실행되도록 조절할 수 있음!
4. useEffect의 잘못된 사용 예
무한 루프를 유발하는 useEffect
useEffect(() => {
setCount(count + 1); // ❌ 잘못된 사용 → 무한 루프 발생
}, [count]);
해결 방법: setCount를 useEffect 내부에서 직접 호출하지 말고, useCallback 또는 useReducer를 활용.
비동기 함수에서 직접 useEffect 사용
useEffect(async () => { // ❌ async 사용하면 안 됨
const data = await fetchData();
setState(data);
}, []);
해결 방법: useEffect 내부에서 비동기 함수를 호출해야 함.
useEffect(() => {
const fetchData = async () => {
const data = await fetchData();
setState(data);
};
fetchData();
}, []);
useLayoutEffect
useLayoutEffect는 React의 Hook 중 하나로, DOM이 업데이트된 직후 동기적으로 실행됩니다.
이는 일반적인 useEffect와는 실행 타이밍이 다르며, 주로 레이아웃 조작 및 측정이 필요할 때 사용됩니다.
1. useLayoutEffect의 실행 타이밍
Hook 실행 시점
| useEffect | 렌더링 후, 브라우저가 화면을 그린 후 (비동기) |
| useLayoutEffect | 렌더링 후, 브라우저가 화면을 그리기 전 (동기적 실행) |
실행 순서
- 컴포넌트가 렌더링됨.
- DOM이 업데이트됨.
- useLayoutEffect 실행 (화면이 사용자에게 보이기 전).
- 브라우저가 화면을 그림.
- useEffect 실행 (비동기적으로 실행됨).
즉, useLayoutEffect는 렌더링 후 바로 실행되며, 브라우저가 화면을 그리기 전에 실행됩니다.
2. useLayoutEffect 기본 문법
import { useLayoutEffect, useState } from "react";
const Example = () => {
const [count, setCount] = useState(0);
useLayoutEffect(() => {
console.log("✅ useLayoutEffect 실행됨!");
}, [count]);
return (
<div>
<p>카운트: {count}</p>
<button onClick={() => setCount(count + 1)}>증가</button>
</div>
);
};
export default Example;
useLayoutEffect는 렌더링 후 바로 실행됨 (화면이 갱신되기 전에 실행).
DOM 조작을 위한 코드를 useLayoutEffect 내부에서 실행할 수 있음.
3. useLayoutEffect가 필요한 경우
1) 레이아웃 측정 (getBoundingClientRect)
컴포넌트가 렌더링된 후, DOM의 크기나 위치를 측정하고 싶을 때 사용.
import { useLayoutEffect, useRef, useState } from "react";
const Example = () => {
const boxRef = useRef(null);
const [boxSize, setBoxSize] = useState({ width: 0, height: 0 });
useLayoutEffect(() => {
if (boxRef.current) {
const { width, height } = boxRef.current.getBoundingClientRect();
setBoxSize({ width, height });
}
}, []);
return (
<div>
<div ref={boxRef} style={{ width: 200, height: 100, background: "blue" }} />
<p>박스 크기: {boxSize.width} x {boxSize.height}</p>
</div>
);
};
export default Example;
useLayoutEffect는 렌더링 후, 브라우저가 화면을 그리기 전에 실행되므로 올바른 크기 측정이 가능.
2) 스타일 변경이 화면 깜빡임 없이 즉시 반영되어야 할 때
import { useState, useLayoutEffect } from "react";
const Example = () => {
const [color, setColor] = useState("blue");
useLayoutEffect(() => {
document.body.style.backgroundColor = color;
}, [color]);
return (
<button onClick={() => setColor(color === "blue" ? "green" : "blue")}>
배경색 변경
</button>
);
};
export default Example;
색상이 변경되면, 깜빡임 없이 즉시 적용됨.
useEffect를 사용하면 잠깐 원래 색상이 보였다가 변경되는 깜빡임(Flickering)이 발생할 수 있음.
useLayoutEffect를 사용하면 DOM이 업데이트되기 전에 변경이 적용되므로 깜빡임 없음.
3) useEffect보다 빠르게 상태 변경이 필요할 때
useEffect는 비동기적으로 실행되므로, UI 업데이트가 눈에 보이게 지연될 수 있습니다.
하지만 useLayoutEffect를 사용하면 즉시 실행됩니다.
import { useEffect, useLayoutEffect, useState } from "react";
const Example = () => {
const [text, setText] = useState("Hello");
useEffect(() => {
console.log("🌐 useEffect 실행됨:", text);
});
useLayoutEffect(() => {
console.log("⚡ useLayoutEffect 실행됨:", text);
});
return (
<button onClick={() => setText("World")}>텍스트 변경</button>
);
};
export default Example;
결과
⚡ useLayoutEffect 실행됨: World
🌐 useEffect 실행됨: World
useLayoutEffect가 먼저 실행됨
useEffect는 화면이 그려진 후 실행됨
4. 언제 useLayoutEffect를 사용해야 할까?
useEffect보다 빠르게 실행되어야 하는 경우
DOM의 크기 또는 위치를 정확하게 측정해야 하는 경우
깜빡임(Flickering) 없이 스타일 변경이 필요한 경우
UI가 예상대로 동작하지 않는 경우 (useEffect 사용 시 레이아웃 오류가 발생할 때)
🚨 주의:
너무 자주 사용하면 렌더링이 차단(blocking)될 수 있음 → 성능 저하 발생 가능
보통의 경우 useEffect를 먼저 사용하고, 필요할 때만 useLayoutEffect를 고려하는 것이 좋음.
useInsertionEffect (React 18+)
useInsertionEffect란?
- React 18에서 도입된 새로운 Hook.
- CSS-in-JS 스타일을 동기적으로 삽입할 때 최적화하기 위해 사용됨.
- useLayoutEffect보다 더 먼저 실행되며, DOM이 업데이트되기 전에 실행됨.
1. useInsertionEffect 실행 타이밍
| useEffect | 렌더링 후 브라우저가 화면을 그린 후 (비동기) |
| useLayoutEffect | 렌더링 후 DOM이 업데이트된 직후 (동기) |
| useInsertionEffect | 렌더링 중(컴포넌트가 React 트리에 삽입되기 전에 실행됨) |
즉, useInsertionEffect는 React의 트리에 컴포넌트가 추가되기 전에 실행됨!
CSS를 DOM 업데이트 전에 삽입하여 스타일 깜빡임(Flickering) 방지.
2. useInsertionEffect 기본 문법
import { useInsertionEffect } from "react";
const Example = () => {
useInsertionEffect(() => {
const style = document.createElement("style");
style.textContent = "body { background-color: lightblue; }";
document.head.appendChild(style);
return () => {
document.head.removeChild(style);
};
}, []);
return <h1>배경색이 동적으로 변경됨</h1>;
};
export default Example;
스타일이 깜빡임 없이 즉시 삽입됨
DOM이 변경되기 전에 실행되므로, 스타일이 반영되기까지의 딜레이가 없음
3. useInsertionEffect vs useLayoutEffect vs useEffect
| useEffect | 브라우저가 화면을 그린 후 실행 | 데이터 패칭, 비동기 작업 |
| useLayoutEffect | DOM이 업데이트된 직후 실행 | DOM 크기 측정, 깜빡임 방지 |
| useInsertionEffect | 컴포넌트가 React 트리에 추가되기 전 실행 | CSS-in-JS 스타일 삽입 최적화 |
즉, useInsertionEffect는 스타일 삽입을 위해 특별히 설계된 Hook입니다.
4. useInsertionEffect가 필요한 이유
1) CSS-in-JS 라이브러리 최적화
- styled-components, emotion 같은 CSS-in-JS 라이브러리는 컴포넌트가 마운트될 때 스타일을 동적으로 삽입합니다.
- 그러나 useEffect 또는 useLayoutEffect를 사용하면 **깜빡임(flash of unstyled content, FOUC)**이 발생할 수 있습니다.
- useInsertionEffect는 React가 DOM을 업데이트하기 전에 실행되므로 깜빡임을 방지할 수 있습니다.
import { useInsertionEffect } from "react";
const useDynamicStyle = (color: string) => {
useInsertionEffect(() => {
const style = document.createElement("style");
style.textContent = `body { background-color: ${color}; }`;
document.head.appendChild(style);
return () => {
document.head.removeChild(style);
};
}, [color]);
};
const Example = () => {
useDynamicStyle("pink");
return <h1>배경색이 핑크로 설정됨</h1>;
};
export default Example;
DOM 업데이트 전 스타일이 즉시 삽입됨 → 스타일 깜빡임 방지!
2) 레이아웃이 변경되기 전에 스타일을 삽입해야 할 때
import { useInsertionEffect, useState } from "react";
const Example = () => {
const [theme, setTheme] = useState("dark");
useInsertionEffect(() => {
const style = document.createElement("style");
style.textContent = theme === "dark"
? "body { background-color: black; color: white; }"
: "body { background-color: white; color: black; }";
document.head.appendChild(style);
return () => {
document.head.removeChild(style);
};
}, [theme]);
return (
<div>
<h1>현재 테마: {theme}</h1>
<button onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>
테마 변경
</button>
</div>
);
};
export default Example;
버튼을 클릭해 테마를 변경할 때, 깜빡임 없이 스타일이 즉시 적용됨!
사용자가 스타일이 바뀌는 순간을 감지할 수 없을 정도로 빠르게 적용 가능.
5. useInsertionEffect는 언제 사용해야 할까?
CSS-in-JS 스타일을 렌더링 전에 삽입해야 할 때
FOUC(깜빡임) 현상을 방지하고 싶을 때
컴포넌트가 React 트리에 삽입되기 전에 실행할 로직이 있을 때
주의! useInsertionEffect는 일반적인 상태 관리에 사용하면 안 됨!
React 공식 문서에서도 "대부분의 경우 useLayoutEffect 또는 useEffect를 사용하면 충분하다."라고 명시되어 있음.
6. useInsertionEffect를 사용하면 안 되는 경우
일반적인 DOM 조작 → useLayoutEffect 사용
비동기 API 요청 → useEffect 사용
컴포넌트의 상태 관리 → useState 또는 useReducer 사용
즉, useInsertionEffect는 정말 특수한 경우 (스타일 최적화)에서만 필요함!
'JavaScript > React' 카테고리의 다른 글
| React 고급 Hook (0) | 2025.03.21 |
|---|---|
| useRef, useImperativeHandle (1) | 2025.03.21 |
| React Router DOM (0) | 2025.03.21 |
| React 기본 개념 (0) | 2025.03.21 |
| React 프로그래밍을 위한 javascript 기본 (0) | 2025.03.21 |