React는 컴포넌트 기반 UI 라이브러리이며, 컴포넌트는 생명주기(Lifecycle)를 갖는다.
이 생명주기는 컴포넌트가 생성되고, 업데이트되며, 제거되는 일련의 과정을 의미한다.
컴포넌트 생명주기 요약
- 마운트(Mount): 컴포넌트가 생성되어 가상 DOM을 거쳐 실제 DOM에 추가되는 시점
- 업데이트(Update): props 또는 state가 변경되어 다시 렌더링되는 시점
- 언마운트(Unmount): 컴포넌트가 가상 DOM과 실제 DOM 양쪽에서 제거되는 시점
1. 마운트 (Mount)
React가 컴포넌트를 처음 렌더링할 때 실행되는 흐름:
- Lazy initializer (게으른 초기화) – useState(() => ...): 마운트 시 1회만 실행됨
- Render – 컴포넌트 함수 실행 → JSX 반환 → 가상 DOM 구성
- React updates DOM – 가상 DOM을 실제 DOM에 반영 (커밋 단계)
- useLayoutEffect 실행 – DOM 반영 직후, 브라우저 렌더링 직전에 동기적으로 실행됨
- 브라우저 렌더링(PAINT) – 실제 DOM이 브라우저에 의해 화면에 그려지는 시점
- useEffect 실행 – 브라우저 렌더링 이후 비동기 실행됨
const Example = () => {
const [count, setCount] = useState(() => {
console.log('1. Lazy initializer 실행');
return 0;
});
useLayoutEffect(() => {
console.log('4. LayoutEffect 실행');
}, []);
useEffect(() => {
console.log('6. Effect 실행');
}, []);
console.log('2. Render 실행');
return <div>{count}</div>;
};
출력: 1. Lazy initializer → 2. Render 실행 → 4. LayoutEffect 실행 → 6. Effect 실행
2. 업데이트 (Update)
컴포넌트가 state나 props 변경으로 인해 다시 렌더링될 때 실행 순서:
업데이트 시 실행 순서 요약
- 렌더 함수 실행 → 가상 DOM 재구성
- React updates DOM → 변경된 가상 DOM을 실제 DOM에 반영
- LayoutEffect cleanup → 이전 useLayoutEffect에서 등록한 정리 함수 실행
- LayoutEffect 실행
- 브라우저 렌더링(PAINT)
- Effect cleanup → 이전 useEffect의 정리 함수 실행
- Effect 실행
const Example = ({ value }) => {
useLayoutEffect(() => {
console.log('LayoutEffect 실행');
return () => console.log('LayoutEffect 정리(cleanup)');
}, [value]);
useEffect(() => {
console.log('Effect 실행');
return () => console.log('Effect 정리(cleanup)');
}, [value]);
console.log('렌더 실행');
return <div>{value}</div>;
};
출력 순서: 렌더 실행 → LayoutEffect cleanup → LayoutEffect 실행 → Effect cleanup → Effect 실행
3. 언마운트 (Unmount)
컴포넌트가 DOM에서 제거될 때 실행되는 cleanup 함수들:
- useLayoutEffect의 cleanup 함수
- useEffect의 cleanup 함수
const Example = () => {
useLayoutEffect(() => {
return () => {
console.log('LayoutEffect 정리 (언마운트)');
};
}, []);
useEffect(() => {
return () => {
console.log('Effect 정리 (언마운트)');
};
}, []);
return <div>Unmount Example</div>;
};
예상 출력: LayoutEffect 정리 (언마운트) → Effect 정리 (언마운트)
useLayoutEffect
🔍 언제, 왜 필요한가?
useLayoutEffect는 가상 돔 계산 결과가 실제 DOM에 반영된 직후, 브라우저가 화면에 그리기 직전에 동기적으로 실행된다.
이 시점에 DOM 측정, 스타일 조정 등 사용자 눈에 보이기 전에 처리해야 하는 작업을 수행할 수 있다.
예를 들어, 어떤 카드 요소가 있고 이 카드에 마우스를 올리면 확장되는 박스가 나타난다고 해보자.
이 박스는 카드의 아래에 보이기를 원하지만, 아래 공간이 부족하면 위에 나타나야 한다.
이걸 구현하려면 다음과 같은 흐름이 필요하다:
- 박스를 일단 카드 아래에 렌더링한다 (이 시점에서는 위치가 정확하지 않을 수 있음)
- useLayoutEffect에서 박스의 실제 높이를 측정한다
- 만약 아래 공간이 부족하면 위로 올려서 다시 렌더링한다
하지만, 이런 렌더링은 모두 브라우저가 화면을 그리기 전에 완료되는것이 좋다.
그렇지 않으면 박스가 아래 언저리에서 위로 "툭" 하고 이동하는 느낌을 사용자에게 줄 수 있다.
따라서 이럴 땐 useLayoutEffect를 써서 측정과 위치 조정을 브라우저 페인팅 전에 마치도록 해야 한다.
useLayoutEffect(() => {
const height = divRef.current.offsetHeight;
if (height > 300) {
divRef.current.style.fontSize = '14px';
}
}, []);
- 이 코드는 브라우저 렌더링 전에 실행되므로, 사용자는 바뀌는 과정을 보지 않고 결과만 본다.
⚠️ useEffect에서 하면?
useEffect(() => {
const height = divRef.current.offsetHeight;
if (height > 300) {
divRef.current.style.fontSize = '14px';
}
}, []);
- 이 코드는 브라우저 렌더링이 끝난 후 실행됨
- 사용자에게는 **기존 스타일이 잠깐 보였다가 바뀌는 현상(깜빡임)**이 생길 수 있음
⚠️ 과도한 사용에 대한 주의
useLayoutEffect는 브라우저가 화면을 그리기 전에 실행되는 동기 함수다.
이 때문에 실행 중에는 브라우저가 페인팅을 멈추고 기다려야 하며, 불필요하게 많이 사용하면 렌더링 성능이 저하될 수 있다.
- DOM 측정이나 위치 보정이 꼭 필요한 상황에서만 최소한으로 사용해야 하며
- 일반적인 효과나 비동기 처리는 useEffect를 사용하는 것이 성능 면에서 유리하다
참고
'FE > React' 카테고리의 다른 글
인공지능 기반 카메라 감지 프로젝트: 백그라운드 동작 이슈 해결기 (1) | 2025.06.30 |
---|---|
왜 memo에서 객체 리터럴은 리렌더링되고, useState 객체는 리렌더링되지 않을까? (1) | 2025.06.28 |
Tailwind CSS vs Styled Components (0) | 2025.03.02 |
useState가 의도대로 동작을 하지 않는다. (0) | 2025.02.22 |
React useRef vs useState (0) | 2025.02.13 |