React에서 상태 기반 렌더링 (State-based Rendering)과 시그널 기반 렌더링 (Signal-based Rendering)은 UI 업데이트 방식에서 큰 차이를 보인다. 두 개념을 비교하면서 자세히 설명하겠습니다.
상태 기반 렌더링 (State-based Rendering)
React의 전통적인 렌더링 방식입니다.
컴포넌트는 state나 props가 변경될 때 전체 컴포넌트를 다시 렌더링합니다.
React는 Virtual DOM을 사용해 변경된 부분만 실제 DOM에 반영하지만, 컴포넌트 함수는 다시 실행됩니다.
작동 방식
- setState 또는 useState로 상태 변경
- React는 해당 컴포넌트와 자식 컴포넌트를 다시 렌더링
- Virtual DOM 비교 후 실제 DOM 업데이트
특징
- 선언적 UI: State -> UI 자동 반영
- 렌더링 단위: 컴포넌트 단위
- 불필요한 렌더링 가능성 있음: memo, useMemo, useCallback 등 최적화 필요
const [count, setCount] = useState(0)
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>
+
</button>
</div>
)
count가 바뀌면 컴포넌트 전체가 다시 렌더링됩니다.
시그널 기반 렌더링 (Signal-based Rendering)
최근 Solid.js, Qwik, React의 실험적 기능에서 주목받는 방식입니다.
UI는 반응형 원자 단위 데이터 (Signal)에 직접 연결됩니다.
변경된 시그널과 관련된 DOM만 업데이트되고, 컴포넌트 전체를 다시 실행하지는 않습니다.
작동 방식
- 시그널은 getter / setter 형태로 값 추적
- 값이 바뀌면 해당 DOM 바인딩만 업데이트
- 컴포넌트 함수는 재실행되지 않음 -> 매우 빠름
특징
- 렌더링 단위: DOM 노드 단위
- 불필요한 렌더링 없음 -> 고성능
- Reactdml useSignal (실험적) 또는 Solid.js의 createSignal과 유사
const count = createSignal(0)
return (
<div>
<p>{count()}</p>
<button onClick={() => count(count() + 1)}>
+
</button>
</div>
)
count()가 바뀌면 <p>만 업데이트되고, 컴포넌트 전체는 재실행되지 않습니다.
React 상태 기반 vs 시그널 기반 렌더링 코드 예제
// 상태 기반 렌더링 (React 기본)
import React, { useState } from 'react'
export default function StateBasedCounter() {
const [count, setCount] = useState(0)
console.log('Component re-rendered') // 상태 변경 시 전체 컴포넌트 재실행
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>
+
</button>
</div>
)
}
- setCount 호출 시 컴포넌트 전체가 재렌더링
- console.log가 매번 찍힘
// 시그널 기반 렌더링 (React 실험적 API, React 19 이상 useSignal API가 실험적으로 제공)
import { useSignal } from 'react'
export default function SignalBasedCounter() {
const count = useSignal(0)
console.log('Component rendered once') // 시그널 변경 시 재실행 없음
return (
<div>
<p>{count.value}</p>
<button onClick={() => count.value++}>
+
</button>
</div>
)
}
- count.value가 바뀌어도 컴포넌트 함수는 재실행되지 않음
- DOM바인딩된 부분만 업데이트 -> 매우 빠름
성능 차이 분석
측정 기준
- 렌더링 횟수: 상태 기반은 변경 시마다 컴포넌트 재실행, 시그널 기반은 최초 1회
- DOM 업데이트 비용: 상태기반은 Virtual DOM diff 필요, 시그널 기반은 직접 DOM 업데이트
- 메모리 사용량: 상태 기반은 컴포넌트 재실행 시 메모리 할당 증가, 시그널 기반은 최소화
실제 시나리오
10,000개의 <Counter />컴포넌트가 있는 리스트에서 버튼 클릭
// 상태 기반
import React, { useState } from 'react'
function Counter({ id }: { id: number }) {
const [count, setCount] = useState(0)
return (
<div>
<p>
Counter {id}: {count}
</p>
<button onClick={() => setCount(count + 1)}>
+
</button>
</div>
)
}
export default function StateBasedList() {
const counters = Array.from({ length: 10000 }, (_, i) => i)
return (
<div>
{counters.map((id) => (
<Counter key={id} id={id} />
))}
</div>
)
}
// 시그널 기반
import { useSignal } from 'react'
function Counter({ id }: { id: number }) {
const count = useSignal(0)
return (
<div>
<p>
Counter {id}: {count.value}
</p>
<button onClick={() => count.value++}>
+
</button>
</div>
)
}
export default function SignalBasedList() {
const counters = Array.from({ length: 10000 }, (_, i) => i)
return (
<div>
{counters.map((id) => (
<Counter key={id} id={id} />
))}
</div>
)
}

- X축: 컴포넌트 개수 (10, 100, 1,000, 10,000)
- Y축: 총 렌더링 시간 (ms)
- 파란색 (State-based): 컴포넌트 수가 많아질수록 선형적 증가
- 초록색 (Signal-based): 훨씬 낮은 렌더링 시간 유지
10,000개 컴포넌트 기준
- 상태 기반: 약 20,000ms (20초)
- 시그널 기반: 약 2,000ms (2초)
10배 이상 빠름 / 규모가 커질수록 차이가 기하급수적으로 커짐
이상 스텔라였습니다 ✍🏻

'TECH' 카테고리의 다른 글
| useOptimistic: React 19의 새로운 기능 🆕 (0) | 2025.12.17 |
|---|---|
| 검색 파라미터 🔍 (0) | 2025.12.15 |
| 5가지 노드 버전 관리자 비교 (0) | 2025.12.04 |
| 아이콘 현지화 (localization) (0) | 2025.12.02 |
| 논리 할당 연산자 (0) | 2025.11.20 |