Реактивность и шаблоны

Что это такое

Шаблон Vue — это обычный HTML плюс несколько дополнительных атрибутов: {{ }} подставляет текст, v-bind и его сокращение : привязывают атрибуты, v-on и его сокращение @ слушают события, а директивы управляют отрисовкой. Под этим лежит реактивность: стоит значению, от которого зависит шаблон, измениться, и Vue перерисовывает те части шаблона, где оно использовалось.

Почему это важно

Реактивность избавляет от ручного обновления представления: состояние вы описываете, а не синхронизируете вручную. В чистом JavaScript, чтобы изменить значение, приходится находить каждое место, которое его показывает, и обновлять каждое по очереди — а один пропущенный случай даёт экран, который лжёт. Vue переворачивает это: вы меняете значение, и фреймворк сам выясняет, что перерисовать. Шаблоны при этом сохраняют разметку читаемой, а не превращают её в вызовы функций, и потому компонент легче передать дизайнеру, а компромисс здесь в том, что выражения в шаблоне менее выразительны, чем произвольный код, поэтому дисциплина требует держать логику в скрипте, а шаблон — декларативным.

Ключевые идеи

Объявите реактивное состояние и ссылайтесь на него

Одно значение оборачивает ref, объект — reactive; и то и другое делает содержимое отслеживаемым. Разворачивает ref шаблон автоматически, а в скрипте вы читаете и записываете .value. Отсюда важное правило: доступ к свойству отслеживается в тот момент, когда он происходит, поэтому добавленное позже свойство не отслеживается, если объект не был реактивным с самого начала.

<script setup>
import { ref } from "vue";

const count = ref(0);
</script>

<template>
  <button @click="count++">{{ count }}</button>
</template>

Привязывайте атрибуты и события сокращениями

: связывает в одну сторону, от состояния к DOM; @ слушает событие. Двустороннюю привязку даёт v-model — сокращение для привязки значения плюс слушатель ввода; он и есть правильный инструмент для полей формы, а не для произвольного состояния.

<template>
  <input v-model="query" placeholder="Поиск" />
  <p v-if="results.length === 0">Ничего не найдено.</p>
  <ul v-else>
    <li v-for="item in results" :key="item.id">{{ item.name }}</li>
  </ul>
</template>

Элементы добавляет и удаляет v-if; v-show лишь переключает отображение и оставляет их на месте. Неверный выбор стоит либо пересозданного компонента, либо постоянно установленного поддерева.

Вычисляемые значения — это кэшированные производные

Значение из реактивного состояния выводит computed и пересчитывает его только при изменении зависимостей. Именно здесь место производным данным — отфильтрованным спискам, суммам, отформатированным подписям, — чтобы шаблоны оставались тонкими, а вывод не повторялся на каждом рендере.

const visible = computed(() =>
  items.value.filter((item) => item.name.includes(query.value))
);

watch — для побочных эффектов

Наблюдатель запускает функцию при изменении значения; для сохранения в хранилище, логирования или запуска запроса это правильная форма, а вот для вычисления значения — неправильная: он вносит лишний цикл рендера и второй источник истины.

Держите шаблон декларативным

Скрипту принадлежит всё, где есть цикл и временная переменная. Шаблоны с длинными выражениями трудно тестировать и невозможно переиспользовать, однако именованное вычисляемое значение или метод дают тот же результат и тестируются отдельно.

Практика

Соберите фильтр через вычисляемое значение

Отрисуйте список и текстовое поле. Список фильтруйте через computed, выведенный из ввода, а не через наблюдатель и второй массив.

Готово, когда: ввод немедленно фильтрует список, очистка поля его восстанавливает, а отфильтрованный массив нигде не хранится в реактивном состоянии.

Сравните v-if и v-show на живом компоненте

Отрисуйте компонент с локальным состоянием сразу внутри двух переключателей — v-if и v-show. Введите значение, переключите туда-обратно и сравните.

Готово, когда: вы можете объяснить, какой вариант потерял введённое значение и какую директиву выберете для часто переключаемой панели.

Частые ошибки

  • Деструктуризация реактивного объекта — извлечённая переменная становится обычным значением и перестаёт обновляться; исправить: храните ссылку или берите помощники реактивности, которые её сохраняют.
  • Забытое .value в скрипте — состояние не меняется и ничего не перерисовывается; исправить: помните, что шаблон разворачивает ref, а скрипт — нет.
  • Вычисление производного состояния в наблюдателе — лишний рендер и второй источник истины, которые могут разойтись; исправить: используйте computed.
  • Индекс массива как :key в v-for — вставка или переупорядочивание переиспользует состояние не того элемента; исправить: задавайте ключ по стабильному id.
  • Бизнес-логика в выражениях шаблона — её нельзя протестировать, и она дублируется, как только шаблону нужен второй такой случай; исправить: вынесите её в вычисляемое значение или метод.

Что читать дальше

  • JavaScript (02-frontend/06-javascript) — реактивность построена на возможностях языка вроде геттеров и прокси; модели легче доверять, когда знаешь язык.
  • Однофайловые компоненты (02-frontend/22-vuejs/02-single-file-components) — как эти шаблоны становятся переиспользуемыми изолированными компонентами.
  • React (02-frontend/29-react) — другой ответ на ту же задачу: никакой реактивности, вместо неё явный сеттер состояния.