005: 고급 반응성

반응형 스토어 생성 및 사용

Svelte 5에서는 $state와 함께 커스텀 반응형 스토어를 만들 수 있습니다. 이를 통해 복잡한 상태 관리 로직을 캡슐화하고 재사용할 수 있습니다.

현재 카운트: 5

컴포넌트 코드:

<script lang="ts">
  function createCountStore(initialValue = 0) {
    let count = $state(initialValue);
    
    return {
      get value() { return count; },
      increment: () => { count += 1; },
      decrement: () => { count -= 1; },
      reset: () => { count = initialValue; }
    };
  }
  
  const counter = createCountStore(5);
</script>

<div class="p-4 border border-orange-500 rounded">
  <p class="text-gray-200">현재 카운트: {counter.value}</p>
  <div class="flex space-x-2">
    <button 
      onclick={() => counter.increment()} 
      class="bg-orange-600 text-white py-1 px-3 rounded hover:bg-orange-500"
    >
      증가
    </button>
    <button 
      onclick={() => counter.decrement()} 
      class="bg-orange-600 text-white py-1 px-3 rounded hover:bg-orange-500"
    >
      감소
    </button>
    <button 
      onclick={() => counter.reset()} 
      class="bg-orange-600 text-white py-1 px-3 rounded hover:bg-orange-500"
    >
      리셋
    </button>
  </div>
</div>

반응성 최적화 기법

대규모 애플리케이션에서는 반응성 시스템을 효율적으로 사용하는 것이 중요합니다. 불필요한 재계산을 피하고 성능을 최적화하는 다양한 기법을 살펴보겠습니다.

총 아이템: 1000

총 값: 50955.59

필터링된 아이템: 1000

  • 아이템 0 - 71.40
  • 아이템 1 - 93.91
  • 아이템 2 - 51.24
  • 아이템 3 - 63.65
  • 아이템 4 - 66.15
  • 아이템 5 - 32.06
  • 아이템 6 - 75.25
  • 아이템 7 - 45.15
  • 아이템 8 - 55.20
  • 아이템 9 - 11.11
페이지 1 / 100

컴포넌트 코드:

<script lang="ts">
  let items = $state(Array.from({ length: 1000 }, (_, i) => ({
    id: i, name: `아이템 ${i}`, value: Math.random() * 100
  })));
  
  let searchTerm = $state('');
  let currentPage = $state(1);
  const itemsPerPage = 10;
  
  let itemCount = $derived(items.length);
  let totalValue = $derived(items.reduce((sum, item) => sum + item.value, 0));
  let filteredItems = $derived(
    searchTerm
      ? items.filter(item => item.name.toLowerCase().includes(searchTerm.toLowerCase()))
      : items
  );
  let paginatedItems = $derived(
    filteredItems.slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage)
  );
  let totalPages = $derived(Math.ceil(filteredItems.length / itemsPerPage));
</script>

<div class="p-4 border border-orange-500 rounded">
  <input
    type="text"
    bind:value={searchTerm}
    placeholder="아이템 검색..."
    class="w-full p-2 rounded bg-zinc-700 text-gray-200 border border-zinc-600"
  />

  <div class="my-4 text-gray-200">
    <p>총 아이템: {itemCount}</p>
    <p>총 값: {totalValue.toFixed(2)}</p>
    <p>필터링된 아이템: {filteredItems.length}</p>
  </div>

  <ul class="space-y-2">
    {#each paginatedItems as item}
      <li class="text-gray-200">
        {item.name} - {item.value.toFixed(2)}
      </li>
    {/each}
  </ul>

  <div class="flex justify-between items-center mt-4">
    <button
      onclick={prevPage}
      disabled={currentPage === 1}
      class="bg-orange-600 text-white py-1 px-3 rounded hover:bg-orange-500 disabled:opacity-50"
    >
      이전
    </button>
    <span class="text-gray-200">페이지 {currentPage} / {totalPages}</span>
    <button
      onclick={nextPage}
      disabled={currentPage === totalPages}
      class="bg-orange-600 text-white py-1 px-3 rounded hover:bg-orange-500 disabled:opacity-50"
    >
      다음
    </button>
  </div>
</div>

Push-Pull 반응성 모델

Svelte 5의 반응성 시스템은 Push와 Pull 모델을 모두 지원합니다. 각 모델의 특징과 적절한 사용 사례를 이해하는 것이 중요합니다.

카운트 (Push): 0

2배 값 (Pull): 0

짝수 여부 (Pull): 짝수

마지막 업데이트: 2:25:51 AM

컴포넌트 코드:

<script lang="ts">
  let count = $state(0);
  let lastUpdate = $state(new Date().toLocaleTimeString());
  
  let double = $derived(count * 2);
  let isEven = $derived(count % 2 === 0);
  
  function increment() {
    count++;
    lastUpdate = new Date().toLocaleTimeString();
  }
</script>

<div class="p-4 border border-orange-500 rounded">
  <div class="text-gray-200 space-y-2">
    <p>카운트 (Push): {count}</p>
    <p>2배 값 (Pull): {double}</p>
    <p>짝수 여부 (Pull): {isEven ? '짝수' : '홀수'}</p>
    <p>마지막 업데이트: {lastUpdate}</p>
  </div>
  
  <button 
    onclick={increment}
    class="bg-orange-600 text-white py-1 px-3 rounded hover:bg-orange-500 mt-4"
  >
    증가 (Push)
  </button>
</div>

의존성 추적 메커니즘

Svelte는 자동으로 반응형 의존성을 추적합니다. 이 메커니즘을 이해하면 더 효율적인 반응형 코드를 작성할 수 있습니다.

전체 이름: 길동홍

인사말: 안녕하세요, 길동홍님!

프로필: 길동홍 (30세)

컴포넌트 코드:

<script lang="ts">
  let firstName = $state('홍');
  let lastName = $state('길동');
  let age = $state(30);
  
  let fullName = $derived(`${lastName}${firstName}`);
  let greeting = $derived(`안녕하세요, ${fullName}님!`);
  let profile = $derived(`${fullName} (${age}세)`);
  
  $effect(() => {
    console.log(`이름이 ${fullName}으로 변경되었습니다.`);
  });
  
  $effect(() => {
    console.log(`나이가 ${age}세로 변경되었습니다.`);
  });
</script>

<div class="p-4 border border-orange-500 rounded">
  <div class="mb-6 space-y-4">
    <div>
      <label for="lastName" class="block text-gray-200 mb-2">성:</label>
      <input
        id="lastName"
        type="text"
        bind:value={lastName}
        class="w-full p-2 rounded bg-zinc-700 text-gray-200 border border-zinc-600"
      />
    </div>
    
    <div>
      <label for="firstName" class="block text-gray-200 mb-2">이름:</label>
      <input
        id="firstName"
        type="text"
        bind:value={firstName}
        class="w-full p-2 rounded bg-zinc-700 text-gray-200 border border-zinc-600"
      />
    </div>
    
    <div>
      <label for="age" class="block text-gray-200 mb-2">나이:</label>
      <input
        id="age"
        type="number"
        bind:value={age}
        class="w-full p-2 rounded bg-zinc-700 text-gray-200 border border-zinc-600"
      />
    </div>
  </div>
  
  <div class="text-gray-200 space-y-2">
    <p>전체 이름: {fullName}</p>
    <p>인사말: {greeting}</p>
    <p>프로필: {profile}</p>
  </div>
</div>

클래스와 함께 $state 사용

Svelte 5에서는 클래스 필드에서도 $state를 사용할 수 있습니다. 이를 통해 객체 지향 프로그래밍과 반응형 프로그래밍을 결합할 수 있습니다.

컴포넌트 코드:

<script lang="ts">
  class Todo {
    done = $state(false);
    text = $state('');
    createdAt = $state(new Date());

    constructor(text: string) {
      this.text = text;
    }

    toggle() {
      this.done = !this.done;
    }

    edit(newText: string) {
      this.text = newText;
    }
  }

  let todos = $state([
    new Todo('Svelte 5 학습하기'),
    new Todo('Runes 시스템 이해하기'),
    new Todo('클래스와 상태 관리 실습하기')
  ]);

  let newTodoText = $state('');

  function addTodo() {
    if (newTodoText.trim()) {
      todos = [...todos, new Todo(newTodoText)];
      newTodoText = '';
    }
  }

  function removeTodo(index: number) {
    todos = todos.filter((_, i) => i !== index);
  }
</script>

<div class="p-4 border border-orange-500 rounded">
  <div class="mb-6 flex space-x-2">
    <input
      type="text"
      bind:value={newTodoText}
      placeholder="새 할 일 입력..."
      class="flex-1 p-2 rounded bg-zinc-700 text-gray-200 border border-zinc-600"
    />
    <button 
      onclick={addTodo}
      class="bg-orange-600 text-white py-1 px-3 rounded hover:bg-orange-500"
    >
      추가
    </button>
  </div>
  
  <ul class="space-y-4">
    {#each todos as todo, i}
      <li class="flex items-center space-x-4 text-gray-200">
        <input
          type="checkbox"
          checked={todo.done}
          onclick={() => todo.toggle()}
          class="form-checkbox h-5 w-5 text-orange-600"
        />
        <input
          type="text"
          value={todo.text}
          onchange={(e: Event) => todo.edit((e.target as HTMLInputElement).value)}
          class="flex-1 p-2 rounded bg-zinc-700 text-gray-200 border border-zinc-600"
        />
        <button 
          onclick={() => removeTodo(i)}
          class="text-red-500 hover:text-red-400"
        >
          삭제
        </button>
      </li>
    {/each}
  </ul>
</div>

실습 과제

고급 상태 관리 시스템 구현하기

지금까지 배운 고급 반응성 개념을 활용하여 다음 기능이 있는 상태 관리 시스템을 구현해보세요:

  • 커스텀 반응형 스토어 설계
  • 중첩된 객체의 효율적인 상태 관리
  • 최적화된 상태 업데이트 구현
  • 클래스 기반 상태 관리자 만들기
  • 의존성 추적을 활용한 자동 업데이트

다음 강의 미리보기

6강: 폼 처리 및 바인딩

다음 강의에서는 Svelte의 폼 처리와 바인딩 기능을 자세히 살펴봅니다:

  • 폼 요소 바인딩
  • 사용자 입력 처리
  • 폼 유효성 검사
  • 커스텀 폼 컴포넌트
  • 파일 업로드 처리

© 2024 Coding Stairs. All rights reserved.