Kurs Svelte i SvelteKit · Moduł 7: Cykl życia

Praktyczne wzorce lifecycle

2 min czytania ·
W tej lekcji4

Auto-save

<script>
  import { onDestroy } from 'svelte';

  let content = $state('');

  // Debounced auto-save
  $effect(() => {
    const draft = content; // odczyt = zależność efektu

    const timeout = setTimeout(() => {
      saveToServer(draft);
    }, 1000);

    // Sprzątanie przed kolejnym uruchomieniem anuluje poprzedni zapis
    return () => clearTimeout(timeout);
  });

  onDestroy(() => {
    // Zapisz przed zamknięciem
    saveToServer(content);
  });

  async function saveToServer(data) {
    await fetch('/api/save', {
      method: 'POST',
      body: JSON.stringify({ content: data })
    });
  }
</script>

<textarea bind:value={content}></textarea>

Efekt śledzi tylko stan odczytany od razu, w jego ciele. Gdyby content był czytany dopiero w setTimeout, efekt nie wiedziałby, że od niego zależy, i nie wróciłby po kolejnej zmianie tekstu.

Subscription cleanup

<script>
  import { onMount } from 'svelte';

  let position = $state({ x: 0, y: 0 });

  onMount(() => {
    function handleMouseMove(e) {
      position = { x: e.clientX, y: e.clientY };
    }

    window.addEventListener('mousemove', handleMouseMove);

    return () => {
      window.removeEventListener('mousemove', handleMouseMove);
    };
  });
</script>

<p>Pozycja: {position.x}, {position.y}</p>

Intersection Observer

<script>
  let element;
  let isVisible = $state(false);

  $effect(() => {
    if (!element) return;

    const observer = new IntersectionObserver(
      ([entry]) => {
        isVisible = entry.isIntersecting;
      },
      { threshold: 0.5 }
    );

    observer.observe(element);

    return () => observer.disconnect();
  });
</script>

<div bind:this={element} class:visible={isVisible}>
  {isVisible ? 'Widoczny!' : 'Niewidoczny'}
</div>

SSR i onMount

W SvelteKit komponent renderuje się najpierw na serwerze, gdzie nie ma window ani localStorage. onMount i $effect nie uruchamiają się na serwerze, ale kod na najwyższym poziomie <script> - już tak:

<script>
  import { onMount } from 'svelte';
  import { browser } from '$app/env'; // SvelteKit 3 (w SvelteKit 2: '$app/environment')

  let windowWidth = $state(0);

  // onMount NIE uruchamia się na serwerze
  onMount(() => {
    windowWidth = window.innerWidth;
  });

  // Kod na najwyższym poziomie działa też podczas SSR,
  // więc API przeglądarki zabezpiecz flagą browser
  const savedName = browser ? localStorage.getItem('viking-name') : null;
</script>

Dostęp do API przeglądarki umieszczaj więc w onMount albo w $effect, a poza nimi sprawdzaj browser.

Kod do tej lekcji: App.svelte
<script>
  // tick() - czekanie na aktualizację DOM
  import { tick } from 'svelte';

  let messages = $state([
    'Witaj w Walhalli!',
    'Tutaj wojownicy odpoczywają'
  ]);
  let inputValue = $state('');
  let listElement;

  async function addMessage() {
    if (!inputValue.trim()) return;

    messages = [...messages, inputValue];
    inputValue = '';

    // Czekaj na aktualizację DOM
    await tick();

    // Teraz DOM jest zaktualizowany
    listElement?.scrollTo({
      top: listElement.scrollHeight,
      behavior: 'smooth'
    });
  }

  // Przykład z tekstem
  let text = $state('Zmień mnie');
  let selection = $state({ start: 0, end: 0 });
  let textareaElement;

  async function transformText() {
    // Zapisz pozycję kursora
    const start = textareaElement.selectionStart;
    const end = textareaElement.selectionEnd;

    // Zmień tekst na wielkie litery
    text = text.toUpperCase();

    // Czekaj na update DOM
    await tick();

    // Przywróć pozycję kursora
    textareaElement.selectionStart = start;
    textareaElement.selectionEnd = end;
  }
</script>

<main class="tick-demo">
  <h1>⏳ tick() - Synchronizacja z DOM</h1>

  <section>
    <h2>Auto-scroll do końca listy</h2>
    <div class="messages" bind:this={listElement}>
      {#each messages as msg}
        <p>{msg}</p>
      {/each}
    </div>
    <div class="input-row">
      <input
        bind:value={inputValue}
        placeholder="Napisz wiadomość..."
        onkeydown={(e) => e.key === 'Enter' && addMessage()}
      />
      <button onclick={addMessage}>Wyślij</button>
    </div>
  </section>

  <section>
    <h2>Zachowanie pozycji kursora</h2>
    <textarea
      bind:this={textareaElement}
      bind:value={text}
      rows="3"
    ></textarea>
    <button onclick={transformText}>WIELKIE LITERY</button>
    <p class="hint">Kursor pozostanie w tym samym miejscu po transformacji</p>
  </section>
</main>

<style>
  .tick-demo {
    font-family: 'Georgia', serif;
    max-width: 500px;
    margin: 0 auto;
    padding: 2rem;
    background: #1a0f0a;
    color: #e8d5b7;
    min-height: 100vh;
  }

  h1 {
    color: #d4af37;
    text-align: center;
  }

  section {
    background: rgba(212, 175, 55, 0.1);
    border: 1px solid #d4af37;
    border-radius: 8px;
    padding: 1rem;
    margin: 1rem 0;
  }

  h2 {
    color: #d4af37;
    font-size: 1rem;
    margin-top: 0;
  }

  .messages {
    height: 150px;
    overflow-y: auto;
    background: rgba(0,0,0,0.3);
    border-radius: 4px;
    padding: 0.5rem;
    margin-bottom: 0.5rem;
  }

  .messages p {
    margin: 0.25rem 0;
    padding: 0.5rem;
    background: rgba(212, 175, 55, 0.1);
    border-radius: 4px;
  }

  .input-row {
    display: flex;
    gap: 0.5rem;
  }

  input, textarea {
    flex: 1;
    padding: 0.5rem;
    background: rgba(0,0,0,0.3);
    border: 1px solid #3d2914;
    border-radius: 4px;
    color: #e8d5b7;
  }

  textarea {
    width: 100%;
    resize: vertical;
    margin-bottom: 0.5rem;
  }

  button {
    padding: 0.5rem 1rem;
    background: #3d2914;
    color: #e8d5b7;
    border: 1px solid #d4af37;
    border-radius: 4px;
    cursor: pointer;
  }

  .hint {
    font-size: 0.85rem;
    color: #8b7355;
    font-style: italic;
    margin-top: 0.5rem;
  }
</style>

Hasła z dokumentacji w tej lekcji

Widzisz błąd w tej lekcji?

Zadania praktyczne w grze

  • Edytor kodu

    Uzupełnij kod, aby usunąć event listener przy odmontowaniu

Przydatne artykuły