Cours Svelte et SvelteKit · Module 7: Cycle de vie

Modèles pratiques de cycle de vie

2 min de lecture ·
Dans cette leçon4

Sauvegarde automatique

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

  let content = $state('');

  // Sauvegarde automatique différée (debounce)
  $effect(() => {
    const draft = content; // lecture = dépendance de l'effet

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

    // Le nettoyage avant la prochaine exécution annule la sauvegarde précédente
    return () => clearTimeout(timeout);
  });

  onDestroy(() => {
    // Sauvegarder avant la fermeture
    saveToServer(content);
  });

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

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

Un effet ne suit que l'état qu'il lit immédiatement, dans son corps. Si content n'était lu que dans setTimeout, l'effet ne saurait pas qu'il en dépend et ne se relancerait pas après la modification suivante du texte.

Nettoyage des abonnements

<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>Position : {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 ? 'Visible !' : 'Invisible'}
</div>

SSR et onMount

Dans SvelteKit, un composant est d'abord rendu sur le serveur, où il n'y a ni window ni localStorage. onMount et $effect ne s'exécutent pas sur le serveur, mais le code au niveau supérieur de <script>, si :

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

  let windowWidth = $state(0);

  // onMount ne s'exécute PAS sur le serveur
  onMount(() => {
    windowWidth = window.innerWidth;
  });

  // Le code de niveau supérieur s'exécute aussi pendant le SSR,
  // protège donc les API du navigateur avec le drapeau browser
  const savedName = browser ? localStorage.getItem('viking-name') : null;
</script>

Place donc l'accès aux API du navigateur dans onMount ou $effect, et en dehors vérifie browser.

Code de cette leçon : App.svelte
<script>
  // tick() - attendre la mise à jour du DOM
  import { tick } from 'svelte';

  let messages = $state([
    'Bienvenue au Valhalla !',
    'Ici reposent les guerriers'
  ]);
  let inputValue = $state('');
  let listElement;

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

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

    // Attendre la mise à jour du DOM
    await tick();

    // Maintenant le DOM est mis à jour
    listElement?.scrollTo({
      top: listElement.scrollHeight,
      behavior: 'smooth'
    });
  }

  // Exemple de texte
  let text = $state('Change-moi');
  let selection = $state({ start: 0, end: 0 });
  let textareaElement;

  async function transformText() {
    // Sauvegarder la position du curseur
    const start = textareaElement.selectionStart;
    const end = textareaElement.selectionEnd;

    // Convertir le texte en majuscules
    text = text.toUpperCase();

    // Attendre la mise à jour du DOM
    await tick();

    // Restaurer la position du curseur
    textareaElement.selectionStart = start;
    textareaElement.selectionEnd = end;
  }
</script>

<main class="tick-demo">
  <h1>⏳ tick() - Synchronisation du DOM</h1>

  <section>
    <h2>Défilement automatique en fin de liste</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="Écris un message..."
        onkeydown={(e) => e.key === 'Enter' && addMessage()}
      />
      <button onclick={addMessage}>Envoyer</button>
    </div>
  </section>

  <section>
    <h2>Conserver la position du curseur</h2>
    <textarea
      bind:this={textareaElement}
      bind:value={text}
      rows="3"
    ></textarea>
    <button onclick={transformText}>MAJUSCULES</button>
    <p class="hint">Le curseur restera à la même position après la transformation</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>

Entrées de la documentation utilisées dans cette leçon

Tu as repéré une erreur dans cette leçon ?

Tâches pratiques dans le jeu

  • Éditeur de code

    Complète le code pour supprimer l'event listener lors du démontage

Articles utiles