Vue
computed: производные значения
В корзине интернет-магазина итоговая сумма зависит от списка товаров, скидки и налога. Можно написать функцию-метод и вызывать её в шаблоне в трёх местах. Но тогда при каждой перерисовке сумма пересчитывается заново все три раза, даже если ничего не менялось. На большом списке это заметные лишние вычисления. Функция computed решает это: результат кешируется и пересчитывается только когда меняется один из его реактивных источников. Это рабочая лошадка любого Vue-компонента.
- Итоговая сумма заказа: производное от списка товаров, количества и скидки, пересчитывается только при их изменении
- Отфильтрованный и отсортированный список из массива и условий фильтра без дублирования логики в шаблоне
- Полное имя из имени и фамилии, форматированная дата, количество выбранных элементов - типовые computed
- Флаг валидности формы, выведенный из значений всех полей, для блокировки кнопки отправки
- Двунаправленная привязка к нормализованному значению через записываемый computed с get и set
Предварительные знания
- Понимание ref и reactive из урока vue-06
- Знакомство с системой отслеживания зависимостей из урока vue-05
- JavaScript: чистые функции, методы массивов filter, map, reduce
- Опыт работы с интерполяцией в шаблоне
computed: значение из состояния
Функция computed принимает функцию-геттер и возвращает реактивное значение, выведенное из других реактивных источников. Когда меняется любой источник, который геттер читает, computed пересчитывается. Это убирает дублирование: логику вывода значения пишут один раз, а не повторяют в каждом месте шаблона.
В примере total выводится из items. Vue отслеживает, что геттер читает items.value, и связывает computed с этим источником. Добавление товара меняет items, и total пересчитывается сам. В шаблоне computed используется как обычное значение - total - без вызова и без .value, потому что верхнеуровневый ref разворачивается автоматически.
computed возвращает ref только для чтения. В скрипте к нему обращаются через total.value, как к обычному ref, но присвоить ему значение напрямую нельзя - это вызовет предупреждение. Запись возможна только у специального записываемого computed, который разбирается в третьем концепте.
Что делает функция computed?
Кеширование: computed против метода
Тот же результат можно получить методом - обычной функцией, вызываемой в шаблоне. Поведение на экране будет одинаковым, но механика разная. computed кеширует результат: он хранит вычисленное значение и отдаёт его без пересчёта, пока не изменится реактивная зависимость. Метод выполняется заново при каждом вызове, потому что Vue не знает, изменится ли его результат, и вызывает его на всякий случай при каждой перерисовке.
- computed (с кешем) — Пересчитывается только когда меняется реактивный источник. Многократное чтение в шаблоне возвращает один и тот же кешированный результат без повторных вычислений.
- Метод (без кеша) — Выполняется при каждом вызове в шаблоне и при каждой перерисовке компонента. Для тяжёлых вычислений это заметная лишняя нагрузка.
В примере метод totalMethod вызывается дважды и при каждой перерисовке выполняется заново оба раза. computed totalComputed вычисляется один раз, а оба обращения в шаблоне получают кешированный результат. На маленьком списке разница незаметна, но на тяжёлой фильтрации или сортировке тысяч элементов кеширование экономит реальное время.
Правило выбора простое: если значение выводится только из реактивного состояния и не зависит от аргументов, берут computed ради кеша. Метод оправдан, когда нужно передавать параметры или когда результат должен пересчитываться при каждом вызове намеренно.
В чём главное практическое преимущество computed перед методом, вызываемым в шаблоне?
Записываемый computed через get и set
По умолчанию computed только читается. Но иногда нужно не только выводить значение, но и реагировать на попытку его записать - например, при двунаправленной привязке к нормализованному виду. Для этого в computed передают не функцию-геттер, а объект с двумя методами: get вычисляет значение, set обрабатывает присваивание и обычно обновляет исходные источники.
В примере fullName читается через get, склеивая имя и фамилию. При вводе в поле через v-model срабатывает set: он разбивает строку и обновляет firstName и lastName. Так computed становится посредником между удобным для пользователя видом (одно поле) и нормализованным хранением (два отдельных значения). Чтение и запись остаются согласованными.
Геттер computed должен оставаться чистым: только вычислять и возвращать значение, без побочных эффектов вроде запросов к серверу, изменения других переменных или работы с DOM. Если внутри геттера менять состояние, поведение станет непредсказуемым. Для побочных эффектов существует watch из следующего урока.
Как создать computed, в который можно не только читать, но и записывать?
Связь с другими темами
Этот урок про производные значения. Дальше идут наблюдатели для побочных эффектов:
- watch и watchEffect — Когда нужен не вывод значения, а побочный эффект на изменение, выбирают наблюдатель, а не computed
- ref и reactive — computed читает реактивные источники, созданные через ref и reactive
Итог
- computed создаёт значение, выводимое из реактивного состояния, и автоматически пересчитывается при изменении его источников
- Ключевое отличие от метода - кеширование: computed возвращает запомненный результат и пересчитывается только когда меняется реактивная зависимость, а метод выполняется при каждом вызове
- computed возвращает ref только для чтения, в шаблоне он разворачивается автоматически, а в скрипте читается через .value
- По умолчанию computed только читается, но через объект с get и set создаётся записываемый computed для двунаправленной привязки
- computed должен быть чистым: только вычислять и возвращать значение, без побочных эффектов вроде запросов или мутаций - для них есть watch
Связанные уроки
- vue-08-watchers — computed и watch оба реагируют на изменения реактивного состояния, но computed возвращает значение, а watch выполняет побочный эффект
- vue-06-ref-reactive — computed строится поверх ref и reactive из предыдущего урока как их производное