Вычисляемые значения: $derived
Часто одно значение полностью зависит от другого: число оставшихся задач — от списка задач, полная цена — от количества и цены. Такие значения не хранят отдельно, а вычисляют руной $derived. Svelte пересчитывает их, когда меняются данные, из которых они получены.
Руна $derived
<!-- src/routes/+page.svelte -->
<script>
let todos = $state([
{ id: 1, text: 'Купить хлеб', done: true },
{ id: 2, text: 'Позвонить Олегу', done: false }
]);
let remaining = $derived(todos.filter((todo) => !todo.done).length);
</script>
<p>Осталось задач: {remaining}</p>
<button onclick={() => (todos[1].done = true)}>Выполнить вторую</button>Сначала на странице «Осталось задач: 1», после клика — «Осталось задач: 0». Вы не пересчитываете remaining вручную: Svelte видит, что выражение читает todos, и обновляет результат при каждом изменении списка.
Несколько вычислений подряд
Вычисляемое значение может опираться на другое вычисляемое:
<script>
let todos = $state([
{ id: 1, text: 'Купить хлеб', done: true },
{ id: 2, text: 'Позвонить Олегу', done: false }
]);
let doneCount = $derived(todos.filter((todo) => todo.done).length);
let percent = $derived(Math.round((doneCount / todos.length) * 100));
</script>
<p>Готово: {percent}%</p>Страница покажет «Готово: 50%». Если отметить вторую задачу, сначала пересчитается doneCount, затем percent, и текст станет «Готово: 100%».
Сложные вычисления: $derived.by
В $derived помещается одно выражение. Если для расчёта нужно несколько строк, используют $derived.by с функцией:
<script>
let todos = $state([
{ id: 1, text: 'Купить хлеб', done: true },
{ id: 2, text: 'Позвонить Олегу', done: false }
]);
let filter = $state('all');
let visible = $derived.by(() => {
if (filter === 'active') return todos.filter((todo) => !todo.done);
if (filter === 'done') return todos.filter((todo) => todo.done);
return todos;
});
</script>
<button onclick={() => (filter = 'active')}>Активные</button>
<p>Показано задач: {visible.length}</p>Сначала показано две задачи, после клика по «Активные» — одна. Значение зависит сразу от todos и filter: изменение любого из них вызывает пересчёт.
$derived или функция
Можно было бы написать функцию getRemaining() и вызывать её в разметке. Но функция выполняется заново в каждом месте, где её вызвали, а $derived считается один раз, запоминает результат и пересчитывается только тогда, когда изменились его зависимости. К тому же его читают как обычную переменную — и в разметке, и в скрипте.
Частая ошибка
Хранить производное значение в $state и обновлять вручную:
<script>
let todos = $state([]);
let remaining = $state(0);
function addTodo(text) {
todos.push({ id: Date.now(), text, done: false });
remaining = remaining + 1;
}
</script>Стоит забыть обновить remaining в одном месте — например, при удалении задачи, — и число на странице разойдётся со списком. Всё, что вычисляется из другого состояния, объявляйте через $derived.