Реактивность и шаблоны — Примеры

Пример 1 — Отфильтрованный список, сохранённый в состоянии

Поле поиска фильтрует список, а отфильтрованный массив записывает в ref наблюдатель. Иногда после очистки поля результаты остаются на экране. Скажите, зачем производное значение вообще хранится, а затем выведите его вместо этого.

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

const items = ref([{ id: 1, name: "Настольная лампа" }]);
const query = ref("");
const visible = ref([]);

watch([items, query], () => {
  visible.value = items.value.filter((item) => item.name.includes(query.value));
}, { immediate: true });
</script>

Решение

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

const items = ref([{ id: 1, name: "Настольная лампа" }]);
const query = ref("");

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

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

Пример 2 — Список с ключом по индексу

Ключ у строк редактируемого списка задан по позиции. Стоит удалить первую строку, и введённые заметки остаются при не тех элементах. Объясните, что ключ сообщает фреймворку, и задайте его по чему-то стабильному.

<template>
  <ul>
    <li v-for="(item, index) in items" :key="index">
      <input v-model="item.note" />
      {{ item.name }}
    </li>
  </ul>
</template>

Решение

<template>
  <ul>
    <li v-for="item in items" :key="item.id">
      <input v-model="item.note" />
      {{ item.name }}
    </li>
  </ul>
</template>

Индекс сообщает фреймворку не что иное, как «то, что стоит на первой позиции», тогда как ключом он сопоставляет отрисованный элемент данным, из которых тот появился: после удаления или переупорядочивания на первой позиции оказывается другой элемент, и фреймворк переиспользует уже стоявшее там поле ввода вместе с введённым в него текстом. С ключом по стабильному id элемент следует за своими данными, поэтому удаляется именно нужная строка, а локальное состояние всех остальных остаётся у своих владельцев. Когда список может меняться или переупорядочиваться, берите для ключа идентичность из предметной области и никогда — позицию в массиве. v-if и v-show — здесь отдельный выбор: один создаёт и уничтожает поддерево, другой лишь скрывает его.