Руна $derived: производные значения
Руна $derived вычисляет значение из другого состояния и пересчитывается только тогда, когда нужно.
«Если значение можно вычислить — не храните его, выводите.» $derived избавляет от рассинхрона между связанными данными.
Часто одно значение зависит от другого. Полная цена корзины зависит от списка товаров. Полное имя зависит от имени и фамилии. Хранить такие значения в отдельном $state и руками их синхронизировать — источник багов: легко забыть обновить производное при изменении исходного. Руна $derived решает это: вы объявляете формулу, а Svelte сам пересчитывает результат, когда меняются зависимости.
$derived в Svelte 5 заменяет реактивные объявления $: из четвёртой версии. Ключевое свойство — мемоизация: выражение пересчитывается, только когда реально изменилась одна из его зависимостей, а не на каждый чих. Если ничего не поменялось, используется закешированное значение.
<script>
let price = $state(100);
let quantity = $state(3);
let total = $derived(price * quantity);
// для сложных вычислений есть $derived.by с телом функции
let label = $derived.by(() => {
return total > 250 ? 'Дорого' : 'Норм';
});
</script>
<p>Итого: {total} ({label})</p>Здесь total автоматически следует за price и quantity. Меняете количество — итог пересчитывается. А label следует уже за total. Получается цепочка зависимостей, которую Svelte отслеживает за вас. Вариант $derived.by нужен, когда вычисление не помещается в одно выражение.
Как это работает под капотом
Смоделируем производное значение на чистом JS. Идея: производное — это функция, которая помнит свои зависимости и пересчитывается лениво, только когда к ней обращаются после изменения источника.
// Упрощённая модель $derived с мемоизацией
function makeReactive(initial) {
let v = initial; const subs = new Set();
return { get:()=>v, set:(n)=>{v=n; subs.forEach(f=>f());}, sub:(f)=>subs.add(f) };
}
function derived(fn, deps) {
let cache, dirty = true;
deps.forEach(d => d.sub(() => { dirty = true; })); // пометить грязным при изменении
return { get() {
if (dirty) { cache = fn(); dirty = false; console.log('пересчёт!'); }
return cache;
}};
}
const price = makeReactive(100), qty = makeReactive(3);
const total = derived(() => price.get() * qty.get(), [price, qty]);
console.log('итого:', total.get()); // пересчёт! -> 300
console.log('итого:', total.get()); // из кэша -> 300 (нет пересчёта)
qty.set(5);
console.log('итого:', total.get()); // пересчёт! -> 500Попробуй сам ▶ — вставь код в консоль браузера (F12 → Console) и нажми Enter, чтобы увидеть вывод.
Обратите внимание на лог: второй вызов total.get() не пересчитывает значение — оно берётся из кэша. Пересчёт случается только после qty.set(5). Это и есть мемоизация, которую $derived даёт бесплатно.
price ($state) --+
|--> total ($derived) --> label ($derived) --> разметка
qty ($state) --+
меняется price/qty -> total помечен грязным -> пересчёт при чтенииЧастые ошибки
- Класть в
$derivedпобочные эффекты. Запись в состояние и сетевые вызовы запрещены — для них есть$effect. - Дублировать данные. Если значение выводится из другого, не храните его в отдельном
$state. - Путать
$derived(expr)и$derived.by(() => ...). Первый берёт выражение, второй — функцию.
Best practices
- Правило выбора: вычисляете значение — берите
$derived; выполняете действие — берите$effect. - Держите выражения внутри
$derivedчистыми и без сайд-эффектов. - Стройте цепочки производных вместо ручной синхронизации связанных значений.
Цепочки вычислений в реальных интерфейсах
В настоящих приложениях производные значения редко существуют поодиночке — они выстраиваются в цепочки. Представьте корзину интернет-магазина: из списка товаров выводится подытог, из подытога и промокода — скидка, из подытога и скидки — итоговая сумма, из суммы — доступность кнопки оформления. Каждое звено описано как $derived от предыдущих, и когда пользователь меняет количество одного товара, Svelte пересчитывает ровно те звенья, которых это касается, и не трогает остальные. Без производных вам пришлось бы вручную пробрасывать изменение через всю цепочку, и любая забытая строка синхронизации стала бы багом. С $derived вы описываете отношения между данными один раз и декларативно — а движок сам поддерживает их в согласованном состоянии. Это превращает класс утомительных и хрупких задач в простое объявление формул.
Итог: $derived описывает значение как формулу от состояния. Svelte мемоизирует результат и пересчитывает его только при изменении зависимостей, избавляя вас от рассинхрона.