Побочные эффекты: $effect
Побочный эффект — действие, которое выходит за пределы разметки компонента: сменить заголовок вкладки, записать данные в localStorage, запустить таймер. Для таких действий есть руна $effect: она выполняет функцию после появления компонента на странице и повторяет её, когда меняется прочитанное в ней состояние.
Первый эффект
<!-- src/routes/+page.svelte -->
<script>
let todos = $state([{ id: 1, text: 'Купить хлеб', done: false }]);
$effect(() => {
document.title = `Задач: ${todos.length}`;
});
</script>
<button onclick={() => todos.push({ id: Date.now(), text: 'Новая', done: false })}>
Добавить
</button>Вкладка браузера получит заголовок «Задач: 1», а после каждого клика число в нём вырастет. Зависимости не нужно перечислять: Svelte сам видит, что функция читает todos.length.
Сохранение в localStorage
Эффект удобен, чтобы сохранять список задач между перезагрузками:
<!-- src/routes/+page.svelte -->
<script>
let todos = $state([]);
// 1. Один раз читаем сохранённый список
$effect(() => {
todos = JSON.parse(localStorage.getItem('todos') ?? '[]');
});
// 2. Сохраняем список при каждом изменении
$effect(() => {
localStorage.setItem('todos', JSON.stringify(todos));
});
</script>
<p>Задач: {todos.length}</p>
<button onclick={() => todos.push({ id: Date.now(), text: 'Новая', done: false })}>
Добавить
</button>Добавьте пару задач и обновите страницу — число сохранится. Эффекты выполняются по порядку и только в браузере, поэтому к localStorage внутри них обращаться безопасно. А вот код в начале <script> SvelteKit выполняет и на сервере, где localStorage нет, — поэтому список читается не в $state(...), а в эффекте. Первый эффект только записывает todos и ничего не читает из состояния, поэтому больше не перезапускается.
Заметьте: JSON.stringify обходит весь массив, поэтому эффект перезапускается при любом изменении внутри него.
Очистка
Если эффект запускает что-то долгоживущее — таймер, подписку, — верните из него функцию очистки. Svelte вызовет её перед повторным запуском эффекта и при удалении компонента:
<script>
let seconds = $state(0);
$effect(() => {
const timer = setInterval(() => seconds++, 1000);
return () => clearInterval(timer);
});
</script>
<p>Вы на странице {seconds} с</p>Число на странице растёт каждую секунду. Когда компонент исчезнет, таймер остановится и не будет работать впустую.
Когда эффект не нужен
Для значений, вычисляемых из состояния, используйте $derived, а для реакции на клик — обработчик события. $effect нужен только для связи с внешним миром: DOM, хранилища браузера, сторонние библиотеки.
Частая ошибка
Менять в эффекте то же состояние, которое он читает:
<script>
let count = $state(0);
$effect(() => {
count = count + 1; // эффект перезапускает сам себя
});
</script>Эффект изменил count, это вызвало его повторный запуск, и так без конца — Svelte остановит цикл с ошибкой. Если нужно новое значение из старого, это задача для $derived или обработчика события.