리액트의 사이드 이팩트 렌더링

이명진·2026년 4월 24일

TIL

목록 보기
27/27

오늘도 BEF 의 렌더링 문제를 풀면서 배운것을 정리해본다.

오늘은 사이드 이팩트 여러개를 정리해본다

오늘 문제는 아래와 같다

import * as React from 'react'
import { useState, useEffect, useLayoutEffect, useInsertionEffect} from 'react'
import { createRoot } from 'react-dom/client'

function App() {
  console.log(1)
  const [state, setState] = useState(0)
  useEffect(() => {
    setState(state => state + 1)
  }, [])

  useEffect(() => {
    console.log(2)
    return () => {
      console.log(3)
    }
  }, [state])

  useEffect(() => {
    console.log(4)
    return () => {
      console.log(5)
    }
  }, [state])

  useLayoutEffect(() => {
    console.log(6)
    return () => {
      console.log(7)
    }
  }, [state])

  useInsertionEffect(() => {
    console.log(8)
    return () => {
      console.log(9)
    }
  }, [state])
  console.log(10)
  return null
}

const root = createRoot(document.getElementById('root'));
root.render(<App/>)

와 정말 복잡하다..
이 문제에서는 useEffect 와 useLayoutEffect , useInsertionEffect 가 등장한다.

useEffect와 useLayoutEffect 는 알고 있었는데
useInsertionEffect는 생소하다..

다시 따로 정리를 해본다

1. useInsertionEffect가 뭐야?

리액트 18에서 추가된 훅으로, "DOM 변경 전"에 실행되는 유일한 이펙트입니다.

  • 목적: CSS-in-JS 라이브러리(예: styled-components, emotion)가 스타일 시트를 동적으로 삽입할 때 사용합니다.
  • 왜 쓰는가: useLayoutEffect나 useEffect에서 스타일을 넣으면 브라우저가 레이아웃을 계산한 뒤에 스타일이 바뀌어 성능이 저하되거나 깜빡임이 생길 수 있습니다. 이를 방지하기 위해 브라우저가 레이아웃을 계산하기 직전에 스타일을 꽂아 넣는 용도입니다.
  • 특징: 컴포넌트 내에서 상태를 변경하거나 ref에 접근하는 등의 일반적인 로직은 권장되지 않습니다.

2. useLayoutEffect 도 다시 정리

"DOM 변경 직후, 브라우저가 화면을 그리기 전(Paint 전)에 동기적으로 실행되는 이펙트"입니다.

  • 목적 : state 변경 → 렌더링 → 화면 그림(Paint) → useEffect 실행 → 다시 state 변경 → 재렌더링 이 과정에서 사용자는 찰나의 순간 동안 잘못된 레이아웃이나 이전 상태의 화면을 보게 됩니다. 이게 바로 "깜빡임" 현상이 있는데 useLayoutEffect를 사용하면 리액트가 화면을 그리기 전에 작업을 가로채서 깜빡임이 사라질수 있기 때문에 사용된다.
  • 특정 : 동기적 실행: 이 훅 내부의 로직이 길어지면 브라우저 화면이 늦게 뜹니다. (사용자는 하얀 화면을 더 오래 보게 됨) 따라서 복잡한 로직이나 데이터 패칭(API 호출)은 절대 여기서 하면 안 됩니다.
  • 서버 사이드 렌더링(SSR): Next.js 같은 환경에서는 서버에 DOM이 없기 때문에 에러 메시지가 뜹니다. 이때는 useEffect를 쓰거나, 클라이언트 사이드인지 체크해야 합니다.

즉, useInsertionEffect 와 useLayoutEffect 는 디자인을 수정할때 사용하게 된다

  • useInsertionEffect: 디자인의 '재료(스타일 시트)'를 준비할 때.
  • useLayoutEffect: 디자인의 '배치(위치, 크기)'를 확정할 때.

그렇다면 실행 순서를 정리해보자

실행순서

리액트는 렌더 단계(Render Phase)와 커밋 단계(Commit Phase)가 나뉘어져 있다

Render Phase

리액트가 계산하는 단계 이다
이 단계에서는 실제 DOM을 건드리지 않습니다. 그냥 가상 DOM(Virtual DOM)을 만들고, 이전 것과 비교해서 "어디를 바꿔야겠네?"라고 계획만 세우는 단계이다.
따라서 콘솔로그는 찍히지만 , 아직화면이나 실제 DOM은 바뀌지 않는 단계

Commit Phase (커밋 단계)

"실제 DOM에 적용하는 단계"
리액트가 렌더 단계에서 세운 계획을 바탕으로 실제 브라우저의 DOM 노드를 생성, 수정, 삭제하게 된다
useInsertionEffectuseLayoutEffect는 바로 이 Commit Phase 도중에 실행되게 된다

전체흐름도

정리 되는 타이밍

useEffect 에서 return을 쓰면 정리가 된다.
나는 처음 정리 타이밍은 useEffect가 실행되기 이전에 정리가 실행되는줄알고있었다. 다만 useEffect훅스가 여러개 일때 하나씩 정리되고 useEffect가 실행되는줄 알았는데 거기서 오해가 있었다.

useEffect 정리 타이밍에서 정리는 모두 모여서 실행이 된다.

왜 이렇게 되는가 싶어서 찾아봤더니 이러한 이유가 있었다

중간 상태의 불일치: 1번 이펙트가 실행되면서 상태를 건드렸는데, 아직 2번 이펙트는 정리조차 안 된 '과거 상태'에 머물러 있다면 로직이 꼬일 수 있습니다.

브라우저 성능: 이펙트 하나 실행할 때마다 브라우저가 레이아웃을 다시 계산해야 할 수도 있는데, 이걸 모아서 처리하면 브라우저가 훨씬 효율적으로 화면을 업데이트할 수 있습니다.

이정도 배웠으니 이제 문제를 풀수 있을 것이다. 다시한번 문제를 보고 풀어보도록 하자! 오늘도 새로운 것을 알고 간다

profile
프론트엔드 개발자 초보에서 고수까지!

0개의 댓글