$effect i cykl życia w Svelte 5: kiedy go używać (a kiedy nie)
Jeśli istnieje jedna Runa Svelte 5, która jest źle rozumiana i nadużywana częściej niż inne, jest nią $effect.
Pochodzi z React, gdzie useEffect i często młotek do każdego gwoździa, i naturalny
spróbuj odtworzyć ten wzór w Svelte. Ale $effect ma inną filozofię, a
węższy zakres zastosowania, a ignorowanie go prowadzi bezpośrednio do najgorszych anty-wzorców frameworka.
Ten przewodnik dokładnie określa, co robi $effect, podobnie jak mechanizm śledzenia
automat działa wewnętrznie, jaka jest złota zasada przy podejmowaniu decyzji, czy go użyć, czy nie, i zapewnia
konkretne przykłady głównych uzasadnionych przypadków użycia: analityka, WebSockets, integracja z bibliotekami
strony trzeciej i synchronizuj z nimi localStorage.
Złota zasada $efektu
Przed napisaniem $effectzadaj sobie pytanie: „czy obliczam coś na podstawie istniejącego stanu?”
Jeśli odpowiedź brzmi tak, użyj $derived. $effect i dla rzeczywistych skutków ubocznych:
operacje, które wchodzą w interakcję ze światem poza interfejsem użytkownika (bezpośredni DOM, sieć, pamięć masowa, timer,
biblioteki zewnętrzne).
Jak działa $effect: automatyczne śledzenie
$effect wykonuje funkcję po aktualizacji modelu DOM. Funkcja
podstawowe i tyle automatycznie śledzi każdy $stan i $pochodne, które odczytuje
jego wykonanie i wykonuje się ponownie za każdym razem, gdy zmienia się jedna z tych zależności.
Nie jest konieczne jawne deklarowanie zależności.
<script lang="ts">
let count = $state(0);
let name = $state('Federico');
// Questo effect dipende da ENTRAMBI count e name
// perche li legge entrambi durante l'esecuzione
$effect(() => {
console.log(`${name} ha un contatore di ${count}`);
// Si ri-esegue quando count O name cambiano
});
// Questo effect dipende SOLO da count
$effect(() => {
document.title = `Contatore: ${count}`;
// Si ri-esegue SOLO quando count cambia
});
</script>
Mechanizm śledzenia korzysta z tego samego systemu proxy co $state: podczas wykonywania
z $effect, każdy dostęp do wartości reaktywnej jest rejestrowany. Kiedykolwiek
zmiany tych wartości, efekt zostaje oznaczony jako „brudny” i zaplanowany do ponownego odtworzenia.
Funkcja czyszczenia
$effect może zwrócić funkcję czyszczącą, która działa przed
przy kolejnym ponownym wykonaniu efektu oraz przy demontażu elementu. Jest to niezbędne dla
unikaj wycieków pamięci dzięki licznikom czasu, subskrypcjom i detektorom zdarzeń:
<script lang="ts">
let wsUrl = $state('wss://api.example.com/stream');
let messages = $state<string[]>([]);
$effect(() => {
// Effect traccia wsUrl: si ri-esegue se l'URL cambia
const ws = new WebSocket(wsUrl);
ws.addEventListener('message', (event) => {
messages.push(event.data);
});
ws.addEventListener('error', (error) => {
console.error('WebSocket error:', error);
});
// Cleanup: chiude la connessione prima della prossima esecuzione
// e quando il componente viene smontato
return () => {
ws.close();
console.log('WebSocket chiuso');
};
});
</script>
Se wsUrl zmiany, Svelte 5 sprząta (zamyka stare połączenie WS),
następnie ponownie wykonuje efekt (otwiera nowe połączenie z nowym adresem URL). I czysty wzór
który nie wymaga ręcznego zarządzania cyklem życia.
Cykl życia: $effect vs onMount vs onDestroy
Svelte 5 utrzymuje funkcje cyklu życia Svelte 4 (onMount, onDestroy,
itp.) dla kompatybilności wstecznej, ale Runy oferują bardziej jednolite podejście:
<script lang="ts">
import { onMount, onDestroy } from 'svelte'; // Svelte 4 style
let data = $state(null);
// --- Svelte 4 pattern ---
onMount(async () => {
data = await fetchData();
});
// --- Svelte 5 equivalente con $effect ---
$effect(() => {
// $effect.root per effects che non dipendono da stato
// e si eseguono una sola volta (equivalente di onMount)
fetchData().then(result => {
data = result;
});
return () => {
// cleanup equivalente a onDestroy
};
});
// --- Svelte 5 per operazioni truly una-tantum ---
// Preferisci il blocco di codice nel componente
// o usa untrack() per evitare tracking accidentale
import { untrack } from 'svelte';
$effect(() => {
// untrack impedisce il tracking di initialValue
const initial = untrack(() => data);
console.log('Valore iniziale (non tracciato):', initial);
// count E tracciato perche e fuori da untrack
console.log('Count corrente:', count);
});
</script>
Uzasadnione przypadki użycia $effect
Oto główne scenariusze, w których $effect i właściwy wybór:
1. Synchronizacja z localStorage
<script lang="ts">
// Leggi il valore iniziale da localStorage (fuori da $effect)
let theme = $state<'light' | 'dark'>(
(localStorage.getItem('theme') as 'light' | 'dark') ?? 'light'
);
// Sincronizza le modifiche a localStorage
$effect(() => {
localStorage.setItem('theme', theme);
document.documentElement.setAttribute('data-theme', theme);
});
</script>
<button onclick={() => theme = theme === 'light' ? 'dark' : 'light'}>
Toggle theme
</button>
2. Analityka i śledzenie
<script lang="ts">
const { pageId, userId }: { pageId: string; userId?: string } = $props();
// Traccia la visualizzazione di pagina quando pageId cambia
$effect(() => {
// Sia pageId che userId sono tracciati
analytics.track('page_view', {
page: pageId,
user: userId ?? 'anonymous',
timestamp: new Date().toISOString()
});
});
</script>
3. Integracja z zewnętrznymi bibliotekami DOM
<script lang="ts">
import Chart from 'chart.js/auto';
let canvasEl: HTMLCanvasElement;
let chartData = $state({ labels: [], datasets: [] });
let chartInstance: Chart | null = null;
$effect(() => {
// Prima esecuzione: crea il chart
if (!chartInstance) {
chartInstance = new Chart(canvasEl, {
type: 'bar',
data: chartData
});
} else {
// Ri-esecuzioni: aggiorna i dati
chartInstance.data = chartData;
chartInstance.update();
}
return () => {
// Cleanup: distruggi il chart
chartInstance?.destroy();
chartInstance = null;
};
});
</script>
<canvas bind:this={canvasEl}></canvas>
4. Obserwator skrzyżowań (leniwe ładowanie, animacje podczas przewijania)
<script lang="ts">
let element: HTMLElement;
let isVisible = $state(false);
let hasBeenVisible = $state(false);
$effect(() => {
const observer = new IntersectionObserver(
(entries) => {
isVisible = entries[0].isIntersecting;
if (isVisible) hasBeenVisible = true;
},
{ threshold: 0.1 }
);
observer.observe(element);
return () => observer.disconnect();
});
</script>
<div
bind:this={element}
class:visible={isVisible}
class:animated={hasBeenVisible}
>
Contenuto che appare con animazione
</div>
Anti-Pattern: nie używaj efektu $ do obliczeń
Najpopularniejszy anty-wzór z $effect i użyj go do aktualizacji stanu pochodzącego z czegoś innego
stan. Tworzy to nieefektywne i potencjalnie nieskończone cykle aktualizacji:
<script lang="ts">
let items = $state([1, 2, 3, 4, 5]);
let total = $state(0); // SBAGLIATO: non dovrebbe essere $state
// ANTI-PATTERN: usare $effect per calcolare un valore derivato
$effect(() => {
total = items.reduce((s, i) => s + i, 0);
// Questo crea: items cambia -> effect corre -> total cambia ->
// potenziali altri effects si ri-eseguono inutilmente
});
// CORRETTO: usare $derived
const totalCorrect = $derived(items.reduce((s, i) => s + i, 0));
</script>
<script lang="ts">
let searchQuery = $state('');
let filteredUsers = $state([]); // SBAGLIATO
// ANTI-PATTERN: filtrare con $effect
$effect(() => {
filteredUsers = users.filter(u =>
u.name.toLowerCase().includes(searchQuery.toLowerCase())
);
});
// CORRETTO: $derived
const filteredUsersCorrect = $derived(
users.filter(u =>
u.name.toLowerCase().includes(searchQuery.toLowerCase())
)
);
</script>
Efekt $ i Nieskończone pętle
Klasyczny błąd i edycja wewnątrz $effect jeden z $ stwierdza, że efekt
ten sam utwór, tworząc nieskończoną pętlę:
// CICLO INFINITO: count tracciato, count modificato
$effect(() => {
console.log(count); // traccia count
count++; // modifica count -> ri-esegue l'effect
});
Svelte 5 wykrywa pętle i zgłasza błąd w rozwoju. Jeśli chcesz zmodyfikować $state z efektu $,
używać untrack() czytać bez śledzenia lub przemyśleć projekt.
$effect.root i $effect.pre
Svelte 5 oferuje dwa wyspecjalizowane warianty $effect:
<script lang="ts">
// $effect.pre: esegue PRIMA dell'aggiornamento del DOM
// Utile per leggere il DOM prima che venga modificato
// (es: salvare la posizione di scroll prima di un aggiornamento)
$effect.pre(() => {
const scrollPos = window.scrollY;
return () => {
// Ripristina scroll dopo l'aggiornamento del DOM
window.scrollTo(0, scrollPos);
};
});
// $effect.root: crea un effetto che non e legato al ciclo di vita
// del componente, utile fuori dai componenti
const cleanup = $effect.root(() => {
$effect(() => {
// Effect che persiste oltre la distruzione del componente
// (utile per singleton e stores globali)
});
return () => { /* cleanup manuale */ };
});
</script>
Testowanie kodu z efektem $
Testuj komponenty za pomocą $effect wymaga uwagi: efekty są zaplanowane
asynchronicznie, więc testy muszą poczekać na zakończenie cyklu aktualizacji:
// test/MyComponent.test.ts
import { render } from '@testing-library/svelte';
import { tick } from 'svelte';
import MyComponent from './MyComponent.svelte';
test('$effect sincronizza con localStorage', async () => {
const { getByRole } = render(MyComponent);
const button = getByRole('button', { name: 'Toggle theme' });
button.click();
// Aspetta che il ciclo di aggiornamento Svelte completi
await tick();
expect(localStorage.getItem('theme')).toBe('dark');
});
Decyzja: $efekt czy $pochodna?
Uproszczony diagram decyzyjny:
- „Czy powinienem obliczyć wartość na podstawie istniejącego stanu?” → użyj
$derived - „Czy muszę mieć operację ze skutkami ubocznymi?” → użyj
$effect - „Czy powinienem bezpośrednio zaktualizować DOM?” → użyj
$effect(a raczej akcja Svelte) - „Czy powinienem wykonać wywołanie API, gdy zmieni się stan?” → użyj
$effect - „Czy powinienem pokazać przekształcone dane w interfejsie użytkownika?” → użyj
$derived
Wnioski i dalsze kroki
$effect i potężny, ale wyspecjalizowany: służy do łączenia responsywnego interfejsu użytkownika Svelte z
świat zewnętrzny — DOM, sieć, pamięć masowa, biblioteki innych firm. Użyj go do odpowiednich obliczeń
a $derived wprowadza niepotrzebną złożoność i potencjalne błędy. Wzór do zapamiętania:
jeśli generuje wartość do wyświetlenia w interfejsie użytkownika, np $derived; jeśli z czymś wchodzi w interakcję
poza interfejsem użytkownika, np $effect.
Następny artykuł dotyczy SvelteKit szczegółowo: jak działają obciążenia funkcje pobierania danych po stronie serwera, w jaki sposób przesyłanie strumieniowe SSR zmniejsza TTFB i w jaki sposób formularze akcje obsługują mutacje bez JavaScript po stronie klienta.
Seria: Svelte 5 i Frontend sterowane kompilatorem
- Artykuł 1: Podejście oparte na kompilatorze i model mentalny
- Artykuł 2: $stan i $pochodne — Uniwersalna reaktywność z runami
- Artykuł 3 (ten): $effect i cykl życia — kiedy go używać (a kiedy nie)
- Artykuł 4: SvelteKit SSR, funkcje przesyłania strumieniowego i ładowania
- Artykuł 5: Przejścia i animacje w Svelte 5
- Artykuł 6: Dostępność w Svelte: Ostrzeżenia kompilatora i najlepsze praktyki
- Artykuł 7: Globalne zarządzanie stanem: kontekst, runy i sklepy
- Artykuł 8: Migracja ze Svelte 4 do Svelte 5 – Poradnik praktyczny
- Artykuł 9: Testowanie w Svelte 5: Vitest, biblioteka testowa i dramaturg







