Cours Svelte et SvelteKit · Module 7: Cycle de vie
Modèles pratiques de cycle de vie
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