Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Вычисляемые значения: $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.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.