Svelte
`$derived`: производные значения
В корзине магазина есть массив товаров, а под ним надо показать итоговую сумму со скидкой и количество позиций. Эти числа не хранят отдельно - их вычисляют из корзины. Если завести для суммы своё состояние `$state` и обновлять его руками при каждом изменении корзины, рано или поздно где-то забудут пересчитать, и сумма соврёт. Руна `$derived` решает это: она объявляет значение как формулу от другого состояния, и Svelte сам пересчитывает его, когда меняется источник.
- Итог корзины: сумма, скидка, число позиций вычисляются из массива товаров через `$derived`
- Фильтрация и поиск: отфильтрованный список - производное от исходных данных и строки запроса
- Валидация формы: флаг можноОтправить выводится из заполненности полей, а не хранится отдельно
- Форматирование: отображаемая цена с разделителями - производная от числового значения
- Прогресс-бары и счётчики, где показатель всегда есть функция от текущего состояния
Предварительные знания
- Руна `$state` и понятие реактивного состояния
- JavaScript: методы массивов reduce, filter, map
- Идея чистой функции: результат зависит только от входов
Объявление производного значения
Руна `$derived` принимает выражение и возвращает значение, которое пересчитывается автоматически. Выражение читает другое реактивное состояние, и Svelte запоминает эти зависимости. Когда любой источник меняется, производное обновляется само. Читают его как обычную переменную, но присваивать ему нельзя: его значение полностью определяется формулой.
Здесь total зависит от price и qty, а withTax зависит от total. Производные образуют цепочку: при изменении qty Svelte пересчитает total, затем withTax, затем обновит разметку. Разработчик нигде не вызывает пересчёт вручную - формулы описывают зависимости, остальное делает компилятор.
- Ручное состояние через `$state` — Сумму держат в отдельном `$state` и обновляют в каждом обработчике, меняющем корзину. Легко забыть одно место и получить рассинхрон
- Производное через `$derived` — Сумма - формула от корзины. Единственный источник истины, пересчёт автоматический, рассинхрон невозможен
Производному значению обычно не присваивают напрямую (с Svelte 5.25 переприсвоение возможно для оптимистичного UI: значение временно переопределяется и пересчитается при следующем изменении зависимостей). Запись total равно 0 не имеет смысла: производное вычисляется только своей формулой. Если значение должно и вычисляться, и редактироваться вручную, это уже обычное `$state`, а не `$derived`.
Что произойдёт с переменной total из `$derived(price * qty)`, когда qty изменится?
`$derived.by` для сложных вычислений
Когда вычисление не умещается в одно выражение - есть ветвления, циклы, промежуточные переменные - используют форму `$derived.by`. Она принимает функцию без аргументов, которая возвращает результат. Внутри можно писать любой код, а Svelte отследит, какое реактивное состояние эта функция читает, и пересчитает результат при его изменении.
Функция перебирает массив, копит сумму и количество и возвращает объект. Это нельзя записать одним коротким выражением, поэтому `$derived.by` удобнее. Поведение то же, что у обычного `$derived`: результат кэшируется и пересчитывается только когда меняется состояние, которое функция читает - здесь массив items.
| Форма | Когда применять |
|---|---|
| `$derived(выражение)` | Одно выражение: арифметика, простой filter или map |
| `$derived.by(() => { ... })` | Многошаговая логика: циклы, ветвления, промежуточные переменные |
Функция в `$derived.by` должна быть чистой относительно реактивности: она читает состояние и возвращает значение, но не меняет другое состояние и не делает побочных эффектов. Если нужно что-то изменить во внешнем мире (запрос, запись в localStorage), это работа для `$effect`, а не для `$derived`.
Когда вместо `$derived(выражение)` стоит использовать `$derived.by`?
Ленивый пересчёт и отслеживание зависимостей
Пересчёт производного ленивый. Значение не вычисляется в момент изменения источника, а только при первом чтении после этого изменения. До тех пор результат не нужен, и Svelte его не считает. После вычисления значение кэшируется и переиспользуется, пока зависимости не поменялись снова. Это экономит работу, когда производное в данный момент нигде не отображается.
Отслеживание зависимостей автоматическое и точное: Svelte фиксирует ровно то состояние, которое формула реально прочитала во время вычисления. Если в выражении есть ветка if, и в текущем вычислении она не выполнилась, то состояние из той ветки в зависимости не попадёт, пока ветка не сработает. Это значит, что производное пересчитывается только на действительно влияющие изменения.
Точное отслеживание - причина, по которой производные дешевы. Цепочка из десятка производных не приводит к лавине пересчётов: меняется один источник, помечаются только зависящие от него производные, а считаются они лишь когда их значение кому-то понадобится.
Что означает ленивый пересчёт производного значения в `$derived`?
Связь с другими темами
`$derived` стоит между состоянием и его отображением:
- `$state`: реактивное состояние — Производные значения вычисляются из источников `$state`
- `$effect`: побочные эффекты — `$effect` тоже реагирует на состояние, но не возвращает значение, а выполняет действие
- Руны: явная реактивность — `$derived` заменил вычисляемую часть метки доллар-двоеточие из Svelte 4
Итог
- Руна `$derived` объявляет значение как формулу от другого реактивного состояния, без ручного пересчёта
- Svelte сам отслеживает, какие источники читает выражение, и пересчитывает производное при их изменении
- Для сложной логики с ветвлениями и циклами есть форма `$derived.by` с функцией, возвращающей значение
- Пересчёт ленивый: производное вычисляется при чтении и кэшируется, пока зависимости не изменились
- Производное состояние обычно не присваивают вручную - его источник истины это формула (с 5.25 допускается временное переопределение)
Связанные уроки
- sv-06-state — `$derived` вычисляет значения из реактивного состояния, поэтому сначала нужен `$state`
- sv-08-effect — `$effect` и `$derived` оба реагируют на состояние, но `$derived` возвращает значение, а `$effect` выполняет действие
- sv-05-runes-intro — `$derived` пришёл на смену части возможностей метки доллар-двоеточие из Svelte 4