Vue

ref() и reactive()

Новичок объявляет const user = reactive({ name: 'Аня' }), всё работает. Потом пишет let count = reactive(0) и счётчик молча перестаёт реагировать. Через час он находит в документации, что reactive не работает с примитивами. Это самый частый затык первой недели на Vue 3. Две функции, ref и reactive, делают похожее, но с разными правилами, и понимание этих правил экономит часы отладки молчаливых багов.

  • Счётчик, флаг загрузки, текст поиска - примитивы, для которых берут ref
  • Объект формы с несколькими полями или объект пользователя профиля - кандидаты на reactive или ref от объекта
  • Состояние, которое нужно целиком заменить новым значением (например, ответ API), почти всегда хранят в ref
  • Списки и массивы оборачивают в ref, потому что переприсваивание массива через reactive теряет реактивность
  • В больших командах часто принимают конвенцию использовать только ref ради единообразия и предсказуемости

Предварительные знания

  • Понимание системы реактивности и Proxy из урока vue-05
  • JavaScript: примитивы против объектов, передача по значению и по ссылке
  • Знакомство со script setup из урока vue-03
  • Базовый опыт работы с шаблонами и интерполяцией

ref: работает для любого значения

Функция ref оборачивает значение в реактивный контейнер. Работает с чем угодно: числами, строками, булевыми, объектами, массивами. В JavaScript-коде значение лежит в свойстве .value, и читать или менять его нужно через него. Причина в том, что примитив нельзя сделать реактивным сам по себе - его невозможно обернуть в Proxy. Поэтому ref оборачивает значение в объект-контейнер со свойством value, а уже за этим свойством Vue следит.

В шаблоне .value писать не нужно: Vue автоматически разворачивает ref верхнего уровня. В примере выше в шаблоне стоит count, а в скрипте count.value. Это сознательное удобство: в разметке короткая запись, а в коде явное .value напоминает, что перед нами реактивный контейнер, а не обычное число.

Забыть .value в скрипте это классическая ошибка. Запись if (count > 5) в скрипте сравнивает объект-контейнер с числом и всегда даёт неверный результат. Правильно if (count.value > 5). Линтер с плагином Vue обычно подсвечивает такие места, но привычка ставить .value в коде важнее.

Почему у ref значение лежит именно в свойстве .value, а не доступно напрямую?

reactive: только для объектов

Функция reactive оборачивает объект или массив в Proxy напрямую, без обёртки value. К свойствам обращаются как у обычного объекта: user.name, user.age. Это выглядит естественнее, чем .value, но у reactive есть ограничения, которых нет у ref, и именно они становятся источником молчаливых багов.

Аспектrefreactive
Что принимаетЛюбое значение, включая примитивыТолько объекты и массивы
Доступ в скриптеЧерез .valueНапрямую к свойствам
Переприсваивание целикомЧерез .value = newObj, реактивность сохраняетсяТеряет реактивность
ДеструктуризацияСохраняет реактивность через toRefsРвёт связь, свойство становится обычным

Два главных подводных камня reactive. Первый: нельзя переприсвоить весь объект новым значением, потому что Proxy привязан к исходной ссылке - новое присваивание создаст новый объект, за которым уже никто не следит. Второй: деструктуризация свойств рвёт реактивность, потому что вытаскивает примитивное значение из Proxy. С ref этих проблем нет: переприсвоение через .value работает, а для деструктуризации есть toRefs.

Важная деталь: ref от объекта внутри использует reactive. Когда пишут ref({ name: 'Аня' }), Vue оборачивает объект в reactive и кладёт под .value. Поэтому доступ user.value.name тоже полностью реактивен. По сути ref это надстройка, которая добавляет поддержку примитивов и переприсваивания поверх reactive.

Почему переприсваивание всего reactive-объекта новым значением ломает реактивность?

Когда что выбирать

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

  • Примитив (число, строка, булево) - всегда ref, reactive здесь просто не работает
  • Значение, которое нужно целиком заменять новым (ответ API, выбранный элемент) - ref, чтобы пережить переприсваивание
  • Массив или список - обычно ref, потому что списки часто заменяют целиком
  • Группа связанных полей без переприсваивания всего объекта - можно reactive ради прямого доступа без .value
  • Единообразие в команде - многие выбирают только ref везде, чтобы не держать в голове два набора правил

Если выбор неочевиден, берут ref. Он покрывает все случаи и не подкидывает сюрпризов с деструктуризацией и переприсваиванием. Это совет не из догмы, а из опыта команд, которые наступали на грабли reactive в реальных проектах.

Для счётчика количества товаров, который увеличивается по кнопке, что разумнее выбрать?

Связь с другими темами

Этот урок про создание реактивного состояния. Дальше на нём строятся производные и наблюдатели:

  • computed — Производные значения берут реактивные источники, созданные через ref и reactive
  • watch и watchEffect — Наблюдатели реагируют на изменения ref и reactive-объектов

Итог

  • ref оборачивает любое значение, включая примитивы, и в скрипте к нему обращаются через .value, тогда как reactive работает только с объектами и массивами
  • В шаблоне ref разворачивается автоматически: пишут count, а не count.value. Свойство .value нужно только в JavaScript-коде
  • Главный подводный камень reactive - потеря реактивности при переприсваивании всего объекта или деструктуризации его свойств
  • ref на объекте внутри хранит reactive: при создании объектного ref доступ к свойствам через .value тоже реактивен
  • Практическое правило: ref как выбор по умолчанию для всего, reactive опционально для группы связанных свойств, которую не переприсваивают целиком

Связанные уроки

  • vue-07-computed — computed возвращает ref только для чтения и строится поверх реактивного состояния из этого урока
  • vue-08-watchers — watch наблюдает именно за ref и reactive-объектами, поэтому их понимание необходимо для наблюдателей
  • vue-05-reactivity-fundamentals — Этот урок углубляет интуицию реактивности из предыдущего в конкретные инструменты
ref() и reactive()

0

1

Войти