현대 웹 개발에서 반응형 프로그래밍은 필수적인 요소가 되었습니다.
특히 Svelte 5에서 도입된 Runes 시스템은 기존의 반응형 프로그래밍 패러다임을 완전히 새롭게 정의했습니다.
이 글에서는 Svelte 5 Runes의 핵심 개념부터 실제 프로젝트 적용까지, 반응형 프로그래밍의 모든 것을 상세히 다루어보겠습니다.
Svelte 5 Runes란 무엇인가?
Svelte 5 Runes는 Svelte의 새로운 반응형 시스템으로, 기존의 $:
구문을 대체하는 혁신적인 접근 방식입니다.
Runes는 '룬 문자'에서 이름을 따온 것으로, 마법적인 기호처럼 코드에 반응성을 부여한다는 의미를 담고 있습니다.
기존 Svelte 4에서는 let
변수의 재할당을 통해 반응성을 구현했지만, Svelte 5 Runes는 더욱 명시적이고 예측 가능한 방식으로 상태 관리를 제공합니다.
이러한 변화는 단순한 문법적 개선을 넘어서, 개발자가 컴포넌트의 반응성을 더 정확하게 제어할 수 있게 해줍니다.
Svelte 공식 문서에 따르면, Runes 시스템은 TypeScript와의 호환성도 크게 향상시켰습니다.
핵심 Runes 종류와 활용법
$state 룬: 반응형 상태 관리의 핵심
$state
룬은 Svelte 5에서 가장 기본적이면서도 중요한 룬입니다.
기존의 let
변수 선언을 대체하며, 더욱 명확한 상태 관리를 제공합니다.
// Svelte 4 방식
let count = 0;
// Svelte 5 Runes 방식
let count = $state(0);
$state
룬의 가장 큰 장점은 중첩된 객체의 반응성을 자동으로 처리한다는 점입니다.
let user = $state({
name: '김개발',
profile: {
age: 30,
skills: ['JavaScript', 'Svelte', 'TypeScript']
}
});
// 깊은 중첩 객체도 자동으로 반응형이 됩니다
user.profile.age = 31; // 자동으로 UI 업데이트
$derived 룬: 계산된 값의 새로운 표준
$derived
룬은 기존의 $:
반응형 구문을 대체하는 강력한 도구입니다.
계산된 값을 더욱 명시적으로 표현할 수 있으며, 의존성 추적이 자동으로 이루어집니다.
let firstName = $state('김');
let lastName = $state('개발');
// 계산된 값 생성
let fullName = $derived(firstName + ' ' + lastName);
// 복잡한 계산도 가능
let userStats = $derived({
totalPosts: posts.length,
averageRating: posts.reduce((sum, post) => sum + post.rating, 0) / posts.length,
lastPostDate: Math.max(...posts.map(post => post.createdAt))
});
$effect : 사이드 이펙트 관리의 혁신
$effect
룬은 컴포넌트의 사이드 이펙트를 관리하는 새로운 방식을 제공합니다.
React의 useEffect
와 유사하지만, 더욱 직관적이고 자동적인 의존성 추적을 제공합니다.
let searchTerm = $state('');
let searchResults = $state([]);
$effect(() => {
// searchTerm이 변경될 때마다 자동 실행
if (searchTerm.length > 2) {
fetchSearchResults(searchTerm).then(results => {
searchResults = results;
});
}
});
실전 프로젝트: Todo 앱으로 배우는 Runes 활용법
실제 프로젝트를 통해 Svelte 5 Runes의 활용법을 살펴보겠습니다.
간단한 Todo 애플리케이션을 만들면서 각 룬의 실전 사용법을 익혀보겠습니다.
기본 Todo 컴포넌트 구조
// TodoApp.svelte
<script>
let todos = $state([]);
let newTodoText = $state('');
let filter = $state('all'); // 'all', 'active', 'completed'
// 필터링된 할일 목록 계산
let filteredTodos = $derived(() => {
switch (filter) {
case 'active':
return todos.filter(todo => !todo.completed);
case 'completed':
return todos.filter(todo => todo.completed);
default:
return todos;
}
});
// 통계 정보 계산
let todoStats = $derived({
total: todos.length,
completed: todos.filter(t => t.completed).length,
remaining: todos.filter(t => !t.completed).length
});
// 로컬 스토리지 동기화
$effect(() => {
localStorage.setItem('todos', JSON.stringify(todos));
});
// 초기 데이터 로드
$effect(() => {
const saved = localStorage.getItem('todos');
if (saved) {
todos = JSON.parse(saved);
}
});
function addTodo() {
if (newTodoText.trim()) {
todos.push({
id: Date.now(),
text: newTodoText.trim(),
completed: false,
createdAt: new Date()
});
newTodoText = '';
}
}
function toggleTodo(id) {
const todo = todos.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed;
}
}
function deleteTodo(id) {
todos = todos.filter(t => t.id !== id);
}
</script>
고급 기능: 검색과 정렬
검색 기능과 정렬 옵션을 추가하여 더욱 실용적인 Todo 앱을 만들어보겠습니다.
let searchQuery = $state('');
let sortBy = $state('createdAt'); // 'createdAt', 'text', 'completed'
// 검색과 정렬이 적용된 할일 목록
let processedTodos = $derived(() => {
let result = [...filteredTodos];
// 검색 적용
if (searchQuery.trim()) {
result = result.filter(todo =>
todo.text.toLowerCase().includes(searchQuery.toLowerCase())
);
}
// 정렬 적용
result.sort((a, b) => {
switch (sortBy) {
case 'text':
return a.text.localeCompare(b.text);
case 'completed':
return a.completed - b.completed;
default:
return new Date(b.createdAt) - new Date(a.createdAt);
}
});
return result;
});
// 검색 결과 통계
let searchStats = $derived({
showing: processedTodos.length,
total: filteredTodos.length,
hasResults: processedTodos.length > 0
});
성능 최적화와 베스트 프랙티스
Runes 사용 시 성능 고려사항
Svelte 5 Runes를 사용할 때 성능을 최적화하는 방법들을 알아보겠습니다.
특히 대용량 데이터를 다룰 때 주의해야 할 점들을 살펴보겠습니다.
// 성능 최적화된 대용량 리스트 처리
let items = $state([]);
let visibleItems = $state([]);
let scrollPosition = $state(0);
// 가상 스크롤링 구현
let virtualizedItems = $derived(() => {
const itemHeight = 50;
const containerHeight = 400;
const startIndex = Math.floor(scrollPosition / itemHeight);
const endIndex = Math.min(
startIndex + Math.ceil(containerHeight / itemHeight) + 1,
items.length
);
return items.slice(startIndex, endIndex).map((item, index) => ({
...item,
index: startIndex + index,
top: (startIndex + index) * itemHeight
}));
});
// 디바운싱된 검색
let searchInput = $state('');
let debouncedSearch = $state('');
$effect(() => {
const timeoutId = setTimeout(() => {
debouncedSearch = searchInput;
}, 300);
return () => clearTimeout(timeoutId);
});
메모리 관리와 정리 작업
효과적인 메모리 관리를 위한 패턴들을 살펴보겠습니다.
// 구독 관리 패턴
let websocket = $state(null);
let messages = $state([]);
$effect(() => {
websocket = new WebSocket('wss://api.example.com/chat');
websocket.onmessage = (event) => {
messages.push(JSON.parse(event.data));
};
// 정리 함수 반환
return () => {
if (websocket) {
websocket.close();
}
};
});
// 이벤트 리스너 관리
let windowWidth = $state(window.innerWidth);
$effect(() => {
const handleResize = () => {
windowWidth = window.innerWidth;
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
});
TypeScript와 Runes 통합
타입 안전성 확보
Svelte 5 Runes는 TypeScript와의 완벽한 통합을 제공합니다.
타입 안전성을 확보하면서 개발 생산성을 높이는 방법을 알아보겠습니다.
interface Todo {
id: number;
text: string;
completed: boolean;
createdAt: Date;
tags?: string[];
}
interface TodoState {
items: Todo[];
filter: 'all' | 'active' | 'completed';
searchQuery: string;
}
// 타입이 지정된 상태 관리
let todoState = $state<TodoState>({
items: [],
filter: 'all',
searchQuery: ''
});
// 제네릭을 활용한 재사용 가능한 훅
function createAsyncState<T>(initialValue: T) {
let data = $state<T>(initialValue);
let loading = $state(false);
let error = $state<Error | null>(null);
return {
get data() { return data; },
get loading() { return loading; },
get error() { return error; },
async load(promise: Promise<T>) {
loading = true;
error = null;
try {
data = await promise;
} catch (e) {
error = e as Error;
} finally {
loading = false;
}
}
};
}
고급 타입 패턴
복잡한 상태 관리를 위한 고급 타입 패턴들을 살펴보겠습니다.
// 상태 머신 패턴
type LoadingState =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: any }
| { status: 'error'; error: Error };
let apiState = $state<LoadingState>({ status: 'idle' });
// 조건부 렌더링을 위한 타입 가드
function isSuccess(state: LoadingState): state is Extract<LoadingState, { status: 'success' }> {
return state.status === 'success';
}
// 타입 안전한 이벤트 시스템
interface EventMap {
'todo:add': { text: string };
'todo:toggle': { id: number };
'todo:delete': { id: number };
}
function createEventSystem<T extends Record<string, any>>() {
let listeners = $state<Map<keyof T, Array<(data: T[keyof T]) => void>>>(new Map());
return {
on<K extends keyof T>(event: K, handler: (data: T[K]) => void) {
if (!listeners.has(event)) {
listeners.set(event, []);
}
listeners.get(event)!.push(handler);
},
emit<K extends keyof T>(event: K, data: T[K]) {
const handlers = listeners.get(event);
if (handlers) {
handlers.forEach(handler => handler(data));
}
}
};
}
마이그레이션 가이드: Svelte 4에서 5로
기존 코드 변환 전략
기존 Svelte 4 프로젝트를 Svelte 5 Runes로 마이그레이션하는 체계적인 접근 방법을 제시합니다.
// Svelte 4 코드
let count = 0;
let doubled;
$: doubled = count * 2;
$: console.log('Count changed:', count);
// Svelte 5 Runes로 변환
let count = $state(0);
let doubled = $derived(count * 2);
$effect(() => {
console.log('Count changed:', count);
});
점진적 마이그레이션 방법
전체 프로젝트를 한 번에 마이그레이션하는 대신, 점진적으로 진행하는 방법을 알아보겠습니다.
// 기존 컴포넌트와 새 Runes 컴포넌트 혼합 사용
// ParentComponent.svelte (Svelte 4 스타일)
<script>
import NewRunesChild from './NewRunesChild.svelte';
let parentData = 'Hello from parent';
</script>
<NewRunesChild data={parentData} />
// NewRunesChild.svelte (Svelte 5 Runes)
<script>
let { data } = $props();
let processedData = $derived(data.toUpperCase());
</script>
<p>{processedData}</p>
실제 프로덕션 사례와 팁
대규모 애플리케이션에서의 Runes 활용
실제 프로덕션 환경에서 Svelte 5 Runes를 효과적으로 활용하는 방법들을 살펴보겠습니다.
// 전역 상태 관리 패턴
// stores/appState.js
class AppState {
user = $state(null);
theme = $state('light');
notifications = $state([]);
// 계산된 속성들
isAuthenticated = $derived(!!this.user);
unreadCount = $derived(this.notifications.filter(n => !n.read).length);
// 액션 메서드들
login(userData) {
this.user = userData;
this.addNotification('환영합니다!', 'success');
}
logout() {
this.user = null;
this.notifications = [];
}
addNotification(message, type = 'info') {
this.notifications.push({
id: Date.now(),
message,
type,
read: false,
createdAt: new Date()
});
}
}
export const appState = new AppState();
컴포넌트 간 통신 패턴
복잡한 컴포넌트 트리에서의 효율적인 데이터 흐름 관리 방법을 알아보겠습니다.
// 컨텍스트를 활용한 상태 공유
// contexts/todoContext.js
import { getContext, setContext } from 'svelte';
const TODO_CONTEXT = Symbol('todo-context');
export function createTodoContext() {
let todos = $state([]);
let filter = $state('all');
const context = {
get todos() { return todos; },
get filter() { return filter; },
addTodo(text) {
todos.push({
id: crypto.randomUUID(),
text,
completed: false
});
},
setFilter(newFilter) {
filter = newFilter;
}
};
setContext(TODO_CONTEXT, context);
return context;
}
export function useTodoContext() {
return getContext(TODO_CONTEXT);
}
결론: Svelte 5 Runes의 미래
Svelte 5 Runes는 단순히 새로운 문법이 아닌, 반응형 프로그래밍의 패러다임을 바꾸는 혁신적인 접근 방식입니다.
명시적이고 예측 가능한 상태 관리를 통해 개발자들은 더욱 안정적이고 유지보수가 용이한 애플리케이션을 구축할 수 있게 되었습니다.
특히 TypeScript와의 완벽한 통합은 대규모 프로젝트에서의 Svelte 활용 가능성을 크게 확장시켰습니다.
앞으로 더 많은 개발자들이 Svelte 5 Runes의 강력함을 경험하게 될 것으로 예상됩니다.
Svelte 커뮤니티에서 최신 정보와 베스트 프랙티스를 지속적으로 공유하며, 함께 성장하는 것이 중요합니다.
이 글에서 다룬 내용들을 바탕으로 여러분만의 혁신적인 웹 애플리케이션을 만들어보시기 바랍니다.
Svelte 5 Runes와 함께하는 반응형 프로그래밍의 새로운 여정이 여러분을 기다리고 있습니다.
관련 참고 자료:
'프론트엔드' 카테고리의 다른 글
Tailwind CSS 커스텀 디자인 시스템 구축하기: 효율적인 프론트엔드 개발을 위한 완벽 가이드 (0) | 2025.06.19 |
---|---|
Vite vs Webpack 2025 - 프론트엔드 빌드 도구 성능 비교 (0) | 2025.06.18 |
Astro로 정적 사이트 생성하기 - Next.js 대안 탐색 (0) | 2025.06.16 |
Micro Frontends 아키텍처로 대규모 프로젝트 관리하기: 모던 프론트엔드 개발의 새로운 패러다임 (0) | 2025.05.26 |
WebAssembly(WASM)으로 프론트엔드 성능 향상시키기: 웹 애플리케이션 성능 최적화 완벽 가이드 (0) | 2025.05.26 |