Wed Dec 17 2025
Svelte 5: новая система реактивности Runes
Svelte 5 представляет Runes — новую систему реактивности, которая меняет подход к управлению состоянием. Runes решают ограничения предыдущей системы, делая код более предсказуемым и удобным для тестирования.
Установка
npm create svelte@latest my-app
# или
npm install svelte@5
Runes
Runes — это специальные символы (функции), которые сообщают компилятору Svelte о реактивных зависимостях. Основные runes: $state, $derived, $effect.
$state
$state создаёт реактивную переменную. При изменении значения компонент автоматически перерисовывается.
<script>
let count = $state(0);
function increment() {
count += 1;
}
</script>
<button onclick={increment}>
Count: {count}
</button>
$derived
<script>
let count = $state(0);
let double = $derived(count * 2);
let isEven = $derived(count % 2 === 0);
</script>
<p>Count: {count}</p>
<p>Double: {double}</p>
<p>Is even: {isEven}</p>
$effect
<script>
let count = $state(0);
let history = $state([]);
$effect(() => {
console.log('Count changed:', count);
history = [...history, count];
});
</script>
$props
<script>
let { title = 'Default', count = 0 } = $props();
</script>
<h1>{title}</h1>
<p>Count: {count}</p>
$props + $derived
<script>
let { items = [] } = $props();
let total = $derived(items.reduce((sum, item) => sum + item.price, 0));
</script>
<p>Total: ${total}</p>
Deep reactivity
<script>
let user = $state({
name: 'John',
email: 'john@example.com'
});
function updateName() {
user.name = 'Jane';
}
</script>
<button onclick={updateName}>
Name: {user.name}
</button>
Snippets
<script>
function greet(name) {
return `Hello, ${name}!`;
}
</script>
{@render greet('World')}
<script>
let { title, children } = $props();
</script>
<div class="card">
<h2>{title}</h2>
{@render children?.()}
</div>
Stores
<script>
import { writable } from 'svelte/store';
const count = writable(0);
</script>
<button onclick={() => count.update(n => n + 1)}>
{$count}
</button>
Event handling
<script>
function handleClick(event) {
console.log(event.target);
}
</script>
<button onclick={handleClick}>Click me</button>
<input oninput={(e) => console.log(e.target.value)} />
Conditional rendering
<script>
let show = $state(false);
</script>
<button onclick={() => show = !show}>Toggle</button>
{#if show}
<p>Now you see me</p>
{/if}
List rendering
<script>
let items = $state([
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' }
]);
</script>
<ul>
{#each items as item (item.id)}
<li>{item.name}</li>
{/each}
</ul>
Class directive
<script>
let active = $state(false);
</script>
<button class:active>Toggle</button>
<style>
.active {
background: blue;
}
</style>
Bindings
<script>
let value = $state('');
</script>
<input bind:value />
<p>{value}</p>
<script>
let checked = $state(false);
</script>
<input type="checkbox" bind:checked />
<p>{checked ? 'Checked' : 'Unchecked'}</p>
Component composition
<!-- Button.svelte -->
<script>
let { children, onclick } = $props();
</script>
<button {onclick}>
{@render children?.()}
</button>
<!-- App.svelte -->
<script>
import Button from './Button.svelte';
</script>
<Button onclick={() => console.log('clicked')}>
Click me
</Button>
Отличия от Svelte 4
Svelte 4:
<script>
import { writable } from 'svelte/store';
let count = writable(0);
$: double = $count * 2;
function increment() {
count.update(n => n + 1);
}
</script>
Svelte 5:
<script>
let count = $state(0);
let double = $derived(count * 2);
function increment() {
count += 1;
}
</script>
Fine-grained reactivity
Svelte 5 отслеживает зависимости на уровне отдельных переменных:
<script>
let user = $state({ name: 'John', age: 30 });
// Перерисовывается только при изменении name
$: console.log('Name:', user.name);
// Перерисовывается только при изменении age
$: console.log('Age:', user.age);
</script>
Runes в действии
$inspect — отладка:
<script>
let count = $state(0);
// Логирование изменений в dev mode
$inspect(count);
// С кастомным тегом
$inspect('counter', count);
</script>
$host — доступ к элементу:
<script>
const input = $host();
function focus() {
input.focus();
}
</script>
<input bind:this={input} />
<button onclick={focus}>Focus</button>
Migration с других фреймворков
Из React:
useState→$stateuseMemo→$deriveduseEffect→$effect- Props →
$props()
Из Vue:
ref()→$state()computed()→$derived()watch()→$effect()
Производительность
Преимущества:
- Компиляция в ванильный JS (нет runtime overhead)
- Tree-shaking неиспользуемого кода
- Минимальный размер бандла (~2KB gzip)
- No virtual DOM — прямые обновления
Бенчмарки:
- Быстрее React при частых обновлениях
- Меньший размер бандла чем Vue/Angular
- Мгновенная загрузка на слабых устройствах
Экосистема
UI библиотеки:
sveltekit— fullstack фреймворк@sveltejs/adapter-*— деплой адаптерыlucide-svelte— иконкиbits-ui— headless компоненты
Инструменты:
svelte-check— типизацияeslint-plugin-svelte— линтингprettier-plugin-svelte— форматирование
SvelteKit
npm create svelte@latest my-app
cd my-app
npm install
npm run dev
Структура:
src/
├── routes/
│ ├── +page.svelte # Главная
│ ├── +layout.svelte # Layout
│ └── api/
│ └── +server.js # API endpoint
├── lib/
│ └── components/
└── app.html
Best Practices
Компоненты:
- Держите компоненты маленькими
- Используйте slots для композируемости
- Избегайте глубокой вложенности
Состояние:
- Используйте
$stateдля локального состояния - Stores для глобального состояния
$derivedдля вычисляемых значений
Производительность:
{#key}блоки для списков{#await}для асинхронных данных- Ленивая загрузка компонентов
Ограничения
- Меньше готовых компонентов чем у React
- Меньше вакансий на рынке
- Экосистема моложе
Когда использовать Svelte 5
Подходит:
- Для приложений с фокусом на производительность
- Команда хочет простой синтаксис
- Критичен размер бандла
Не подходит:
- Нужна большая экосистема
- Команда уже знает React/Vue
- Требуется много готовых компонентов