본문 바로가기
프론트엔드

Svelte 5 Runes로 반응형 프로그래밍하기: 차세대 리액티브 시스템 완벽 가이드

by devcomet 2025. 6. 17.
728x90
반응형

Svelte 5 Runes로 반응형 프로그래밍하기: 차세대 리액티브 시스템 완벽 가이드
Svelte 5 Runes로 반응형 프로그래밍하기: 차세대 리액티브 시스템 완벽 가이드

 

현대 웹 개발에서 반응형 프로그래밍은 필수적인 요소가 되었습니다.

특히 Svelte 5에서 도입된 Runes 시스템은 기존의 반응형 프로그래밍 패러다임을 완전히 새롭게 정의했습니다.

이 글에서는 Svelte 5 Runes의 핵심 개념부터 실제 프로젝트 적용까지, 반응형 프로그래밍의 모든 것을 상세히 다루어보겠습니다.


Svelte 5 Runes란 무엇인가?

Svelte 5 Runes는 Svelte의 새로운 반응형 시스템으로, 기존의 $: 구문을 대체하는 혁신적인 접근 방식입니다.

Runes는 '룬 문자'에서 이름을 따온 것으로, 마법적인 기호처럼 코드에 반응성을 부여한다는 의미를 담고 있습니다.

기존 Svelte 4에서는 let 변수의 재할당을 통해 반응성을 구현했지만, Svelte 5 Runes는 더욱 명시적이고 예측 가능한 방식으로 상태 관리를 제공합니다.

Svelte 4 vs Svelte 5 비교 다이어그램
Svelte 4 vs Svelte 5 비교 다이어그램

 

이러한 변화는 단순한 문법적 개선을 넘어서, 개발자가 컴포넌트의 반응성을 더 정확하게 제어할 수 있게 해줍니다.

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))
});

 

Runes 시스템 플로우차트
Runes 시스템 플로우차트

$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
});

 

Todo 앱 인터페이스 스크린샷
Todo 앱 인터페이스 스크린샷


성능 최적화와 베스트 프랙티스

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와 함께하는 반응형 프로그래밍의 새로운 여정이 여러분을 기다리고 있습니다.


관련 참고 자료:

728x90
반응형