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, и именно они становятся источником молчаливых багов.
| Аспект | ref | reactive |
|---|---|---|
| Что принимает | Любое значение, включая примитивы | Только объекты и массивы |
| Доступ в скрипте | Через .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 — Этот урок углубляет интуицию реактивности из предыдущего в конкретные инструменты