jblog
스벨트를 통해 리액트를 더 잘 이해하기
Review

스벨트를 통해 리액트를 더 잘 이해하기

FEConf 2025 발표 리뷰 — 왜 모든 프레임워크가 시그널로 가는데 리액트만 안 갈까?

2025-08-1213 min readfeconf, svelte, react, frontend

#리액트만 빼고 다 시그널이라고?

FEConf 2025에서 가장 인상 깊었던 발표를 꼽으라면, 단연 "스벨트를 통해 리액트 더 잘 이해하기" 였다.

발표자분은 스스로를 "스벨트 전도사"라고 소개하셨는데, 정말로 전도당할 뻔했다.

전도 시작

이 발표가 단순히 "스벨트가 좋아요~"가 아니라, 스벨트의 렌더링 방식을 이해하면 리액트가 왜 이렇게 동작하는지 더 깊이 이해할 수 있다는 관점이어서 굉장히 흥미로웠다.

사실 나는 그동안 리액트 외의 프레임워크에 관심이 별로 없었다.

"리액트가 시장 점유율 1등인데 다른 걸 왜 배워?" 라는 생각이었다.

하지만 이 발표를 보고 나서 생각이 완전히 바뀌었다.


모든 프레임워크의 흐름: 시그널(Signals)

발표의 핵심 메시지는 이것이었다.

Vue, SolidJS, Preact, Angular, Svelte... 모든 프레임워크가 시그널 기반으로 가고 있다. 리액트만 빼고.

시그널이 뭔지부터 정리해보자.

기존 리액트의 방식: Virtual DOM

function Counter() {
  const [count, setCount] = useState(0);
 
  return (
    <div>
      <h1>카운터</h1>          {/* 안 바뀜 */}
      <p>현재 : {count}</p>  {/* 바뀜 */}
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

count가 바뀌면 리액트는 어떻게 할까?

  1. 컴포넌트 함수 전체를 다시 실행한다 (리렌더링)
  2. 새로운 Virtual DOM 트리를 만든다
  3. 이전 Virtual DOM과 비교한다 (diffing)
  4. 바뀐 부분만 실제 DOM에 반영한다

<h1>카운터</h1>은 안 바뀌었는데, 비교하는 과정을 거쳐야 한다.

컴포넌트가 100개면? 100번 비교해야 한다.

시그널의 방식: Fine-Grained Reactivity

시그널은 완전히 다른 접근을 한다.

// Svelte 5의 Runes 문법
let count = $state(0);
 
// count가 바뀌면, count를 사용하는 DOM 노드"만" 업데이트
// 컴포넌트 전체를 다시 실행하지 않음!

바뀐 값을 사용하는 딱 그 DOM 노드만 직접 업데이트한다.

Virtual DOM을 만들 필요도 없고, 비교할 필요도 없다.

리액트:   상태 변경 → 컴포넌트 재실행 → VDOM 생성 → VDOM 비교 → DOM 업데이트
시그널:   상태 변경 → 해당 DOM 노드 직접 업데이트

속도 차이

이론적으로 시그널이 더 빠를 수밖에 없다. 중간 과정이 없으니까.


Svelte 5의 Runes: 시그널의 진화

Svelte 5에서 도입된 Runes는 시그널 기반의 반응성 시스템이다.

이전 Svelte에서는 let만으로 반응성이 생겼는데 (마법처럼), Svelte 5에서는 더 명시적으로 바뀌었다.

<script>
  // $state — 반응형 상태 선언
  let count = $state(0);
 
  // $derived — 파생값 (React의 useMemo와 유사)
  let doubled = $derived(count * 2);
 
  // $effect — 부수 효과 (React의 useEffect와 유사)
  $effect(() => {
    console.log(`count가 바뀜: ${count}`);
  });
</script>
 
<button onclick={() => count++}>
  {count} × 2 = {doubled}
</button>

리액트와 비교해보면,

Svelte 5 RunesReact Hooks차이점
$state(0)useState(0)Svelte는 직접 할당 가능 (count++)
$derived(count * 2)useMemo(() => count * 2, [count])Svelte는 의존성 배열 불필요
$effect(() => {...})useEffect(() => {...}, [deps])Svelte는 자동으로 의존성 추적

의존성 배열이 없다. 이게 Svelte의 가장 큰 매력이다.

리액트에서 useEffect의 의존성 배열 때문에 얼마나 많은 버그가 생기는지 생각해보면...

의존성 배열 지옥

Svelte의 컴파일러가 코드를 분석해서 자동으로 의존성을 추적한다. 개발자가 직접 관리할 필요가 없다.


벤치마크: 진짜로 빠른가?

발표에서 인상적이었던 부분은 실제 벤치마크 결과였다.

React 19 Compiler vs Svelte 5의 비교에서,

지표React 19Svelte 5차이
INP (Interaction to Next Paint)68ms24ms약 3배 빠름
번들 크기~45KB (runtime)~2KB (컴파일 결과)약 22배 작음
메모리 사용량상대적으로 높음상대적으로 낮음

특히 INP에서 3배 차이는 체감이 확실히 된다.

고빈도 업데이트 시나리오(드래그 앤 드롭, 실시간 차트 등)에서 차이가 두드러진다.

이전에 토스의 Deus팀 리뷰에서 MobX를 선택한 이유가 Fine-Grained Reactivity 때문이라고 했는데, 바로 이 맥락이었다.

Deus 같은 디자인 편집기에서는 드래그할 때마다 60fps가 유지되어야 하니까, Virtual DOM의 오버헤드가 문제가 된 것이다.


그럼 리액트는 왜 시그널을 안 쓸까?

이게 발표에서 가장 흥미로웠던 질문이다.

모든 프레임워크가 시그널로 가는데, 리액트 팀은 왜 Virtual DOM을 고수할까?

리액트 팀의 철학

리액트 팀의 대답은 이렇다.

"컴파일러가 충분히 똑똑해지면, Virtual DOM의 오버헤드는 무시할 수 있을 정도로 줄어든다."

React 19의 Compiler는 자동으로 메모이제이션을 적용해서, useMemo, useCallback, React.memo를 직접 쓸 필요를 없앤다.

즉, 리액트는 시그널로 갈아타는 대신 컴파일러로 Virtual DOM을 최적화하는 방향을 선택한 것이다.

내 생각

솔직히 두 접근 모두 일리가 있다고 생각한다.

시그널 진영의 논리: "중간 과정(VDOM) 자체를 없애는 게 근본적인 해결이다."

리액트 진영의 논리: "개발자 경험(DX)이 더 중요하다. 컴파일러가 알아서 최적화해줄 것이다."

나는 리액트의 멘탈 모델이 더 직관적이라고 느낀다.

"상태가 바뀌면 컴포넌트가 다시 실행된다"는 모델은 정말 단순하다. 예측 가능하다.

반면 시그널은 "이 값이 바뀌면 이 DOM 노드만 바뀐다"인데, 복잡한 앱에서는 추적이 어려울 수도 있다.

하지만 성능이 중요한 상황에서는 시그널의 장점이 압도적이다.

토스의 Deus처럼 고빈도 업데이트가 필요한 앱에서는 시그널 기반이 맞는 선택일 수 있다.

결국은 상황에 맞는 도구를 선택하는 것이 중요하다.


ssgoi: 스벨트의 가능성을 보여준 라이브러리

발표자분이 직접 만든 ssgoi라는 라이브러리 시연도 인상적이었다.

스벨트만으로, 외부 라이브러리 하나 없이 모바일 수준의 페이지 전환 애니메이션을 구현했다.

iOS의 네비게이션 전환 같은 부드러운 페이지 이동을 웹에서 구현한 것인데,

이게 가능한 이유가 바로 Svelte의 컴파일 타임 최적화 덕분이다.

런타임 오버헤드가 거의 없으니, 애니메이션에 모든 리소스를 집중할 수 있는 것이다.


리액트 개발자로서 배운 점

나는 앞으로도 리액트를 주로 쓸 것이다. 시장 점유율이나 생태계를 무시할 수는 없으니까.

하지만 이 발표를 통해 확실히 깨달은 것들이 있다.

1. 리렌더링에 대한 깊은 이해

리액트의 리렌더링이 "문제"가 아니라 "설계" 라는 것을 이해하게 됐다.

리액트 팀은 의도적으로 "컴포넌트 전체 재실행"이라는 단순한 모델을 선택한 것이다. 그리고 그 대가로 VDOM 비교라는 비용을 지불하고 있다.

2. 왜 useMemouseCallback이 필요한지

시그널 프레임워크에서는 이런 것들이 필요 없다. 값이 바뀌면 그 값을 쓰는 곳만 업데이트되니까.

리액트에서 useMemouseCallbackVDOM 비교 비용을 줄이기 위한 최적화다.

React Compiler가 이걸 자동화하려는 이유도 이제 이해가 된다.

3. 다른 프레임워크를 배우는 것의 가치

스벨트를 배운다고 해서 리액트를 버리는 게 아니다.

다른 관점으로 같은 문제를 바라보면, 내가 쓰는 도구를 더 깊이 이해하게 된다.

이전에 "좋은 개발자란 무엇일까?" 글에서 "You write what you read"라고 했는데,

프레임워크도 마찬가지인 것 같다. 다양한 프레임워크의 접근 방식을 읽으면, 더 좋은 코드를 쓸 수 있다.

깨달음


정리하며

이 발표를 한 줄로 요약하면,

스벨트를 배우면 리액트를 더 깊이 이해할 수 있다.

프레임워크 전쟁은 의미 없다. 각각의 선택에는 이유가 있고, 그 이유를 이해하는 것이 개발자의 성장이다.

나는 이번 발표를 계기로 Svelte 5의 Runes를 직접 사용해볼 생각이다. 물론 프로덕션에 쓰겠다는 게 아니라, 리액트를 더 잘 쓰기 위해 스벨트를 공부하겠다는 것이다.

리액트만 써온 프론트엔드 개발자라면, 한 번쯤 다른 프레임워크의 세계를 구경해보는 것을 강력 추천한다.

세상이 달라 보인다.


#Reference

댓글

댓글을 불러오는 중...