Svelte 5 Runes 완전 정복: React 개발자를 위한 전환 가이드
React를 쓰다 보면 한 번쯤 이런 생각이 든다. "왜 의존성 배열을 일일이 관리해야 하지?" "useMemo를 언제 써야 하는 거지?" Svelte 5는 이 불편함을 근본적으로 해결한다. 바로 **Runes(룬)**라는 새로운 반응성 시스템을 통해서다.
이 글에서는 React 개발자의 시각으로 Svelte 5 Runes를 분석하고, 실제 코드를 비교하면서 전환 방법을 단계별로 안내한다.
Runes란 무엇인가?
Runes는 Svelte 5에서 도입된 컴파일러 기반 반응성 프리미티브다. $ 접두사가 붙은 특별한 함수 형태로 제공되며, Svelte 컴파일러가 이를 분석해 최적화된 바닐라 JavaScript를 생성한다.
핵심 Runes는 다음 네 가지다:
- $state: 반응형 상태 선언
- $derived: 다른 상태에서 파생된 계산값
- $effect: 상태 변화에 따른 부작용 처리
- $props: 컴포넌트 props 선언
가장 큰 장점은 컴포넌트 외부에서도 사용 가능하다는 것이다. .ts 혹은 .js 파일에서도 Runes를 쓸 수 있어, React 훅의 "반드시 컴포넌트 안에서만" 규칙에서 벗어날 수 있다.
React vs Svelte 5 Runes: 핵심 비교
| 기능 | React (Hooks) | Svelte 5 (Runes) |
|---|---|---|
| 상태 선언 | useState(0) | $state(0) |
| 계산된 값 | useMemo(() => ...) | $derived(...) |
| 부작용 | useEffect(() => ..., [deps]) | $effect(() => ...) |
| Props | 함수 인자 | $props() |
| 의존성 관리 | 수동 (배열) | 자동 (컴파일러 추적) |
| 사용 위치 | 컴포넌트 내부만 | 어디서든 가능 |
| 가상 DOM | 사용 | 미사용 (직접 DOM 조작) |
| 번들 크기 | 상대적으로 큼 | 매우 작음 |
1. 상태 관리: useState → $state
React에서 가장 기본적인 상태 관리 방식인 useState는 Svelte 5에서 $state로 대체된다.
React 방식:
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
클릭 횟수: {count}
</button>
);
}
Svelte 5 방식:
<script>
let count = $state(0);
</script>
<button onclick={() => count++}>
클릭 횟수: {count}
</button>
차이가 보이는가? Svelte 5는 직접 변수에 할당하면 반응성이 트리거된다. setter 함수가 필요 없다. 또한 $state는 객체나 배열에도 깊은 반응성(Deep Reactivity)을 제공한다.
<script>
let user = $state({ name: '김개발', age: 28 });
function birthday() {
user.age++;
}
</script>
<p>{user.name}님, {user.age}세</p>
<button onclick={birthday}>생일 축하!</button>
2. 계산된 값: useMemo → $derived
React에서 성능 최적화를 위해 useMemo를 쓰는 패턴은 Svelte 5에서 $derived로 훨씬 간결해진다.
React 방식:
import { useState, useMemo } from 'react';
function Cart({ items }) {
const total = useMemo(
() => items.reduce((sum, item) => sum + item.price * item.qty, 0),
[items]
);
return <p>합계: {total}원</p>;
}
Svelte 5 방식:
<script>
let { items } = $props();
let total = $derived(
items.reduce((sum, item) => sum + item.price * item.qty, 0)
);
</script>
<p>합계: {total}원</p>
의존성 배열이 사라졌다. Svelte 컴파일러가 $derived 내부에서 참조하는 반응형 값을 자동으로 감지해 추적한다. 복잡한 로직이라면 $derived.by를 사용하면 된다.
<script>
let numbers = $state([1, 2, 3, 4, 5]);
let stats = $derived.by(() => {
const sum = numbers.reduce((a, b) => a + b, 0);
const avg = sum / numbers.length;
return { sum, avg };
});
</script>
<p>합계: {stats.sum}, 평균: {stats.avg}</p>
3. 부작용: useEffect → $effect
React의 useEffect는 의존성 배열을 빠뜨리거나 잘못 지정하면 버그가 생기는 대표적인 함정이다. Svelte 5의 $effect는 이 문제를 해결한다.
React 방식:
import { useState, useEffect } from 'react';
function TitleSync() {
const [title, setTitle] = useState('홈');
useEffect(() => {
document.title = title + ' - 코드픽';
}, [title]);
return <input value={title} onChange={e => setTitle(e.target.value)} />;
}
Svelte 5 방식:
<script>
let title = $state('홈');
$effect(() => {
document.title = title + ' - 코드픽';
});
</script>
<input bind:value={title} />
클린업 함수도 React와 동일하게 반환값으로 처리한다.
<script>
$effect(() => {
const id = setInterval(() => {
console.log('틱');
}, 1000);
return () => clearInterval(id);
});
</script>
4. Props 선언: $props
React 함수형 컴포넌트에서 props를 구조 분해하는 방식과 유사하지만, Svelte 5는 $props()를 통해 명시적으로 선언한다.
React 방식:
function UserCard({ name, role = '사용자' }) {
return <div>{name} ({role})</div>;
}
Svelte 5 방식:
<script>
let { name, role = '사용자' } = $props();
</script>
<div>{name} ({role})</div>
타입스크립트와 함께 사용할 때는 다음과 같이 쓴다.
// UserCard.svelte
<script lang="ts">
interface Props {
name: string;
role?: string;
}
let { name, role = '사용자' }: Props = $props();
</script>
5. 컴포넌트 외부에서 상태 공유
React에서 전역 상태를 공유하려면 Context API나 Redux 같은 외부 라이브러리가 필요하다. Svelte 5 Runes는 .svelte.ts 파일에서도 사용 가능해, 별도 라이브러리 없이 반응형 전역 상태를 만들 수 있다.
// store/auth.svelte.ts
let user = $state(null);
export function getUser() { return user; }
export function login(name, token) {
user = { name, token };
}
export function logout() {
user = null;
}
<!-- App.svelte -->
<script>
import { getUser, logout } from './store/auth.svelte.ts';
let user = $derived(getUser());
</script>
{#if user}
<p>환영합니다, {user.name}님!</p>
<button onclick={logout}>로그아웃</button>
{/if}
마이그레이션 팁
- 점진적 전환 가능: Svelte 5는 Svelte 4 문법과 공존한다. 한 번에 전환하지 않아도 된다.
- 자동 마이그레이션 도구 활용:
npx sv migrate svelte-5명령으로 기본적인 코드 변환이 가능하다. - $effect 남용 주의: React의 useEffect 습관으로 모든 것을 $effect로 처리하지 말 것. 가능하면 $derived를 우선 검토하라.
- 깊은 반응성 주의: $state로 선언한 객체는 내부 속성까지 모두 반응형이다. 성능에 민감하다면 $state.raw를 고려하라.
코드벤터는 Svelte 5와 같은 최신 프론트엔드 기술을 활용해 효율적인 웹 서비스를 개발합니다. React에서 Svelte로의 전환은 단순히 문법 교체가 아니라, 더 직관적이고 성능 좋은 코드를 향한 패러다임 전환입니다. 코드벤터의 개발 철학처럼, 복잡한 것을 단순하게 - Svelte 5 Runes가 그 방향을 제시합니다.