Skip to main content

Ref проти reactive у Vue.js

ref проти reactive у Vue.js: ref обгортає будь-яке значення в реактивний контейнер з доступом через .value; reactive перетворює об'єкт на глибокий проксі з прямим доступом до властивостей.

Теорія

TL;DR

  • ref = коробка навколо значення; в скрипті дістаєш через .value, у шаблоні розпаковується автоматично
  • reactive = живий проксі об'єкта; звертаєшся до властивостей напряму, без обгортки
  • ref приймає примітиви і об'єкти; reactive тільки об'єкти (на примітивах кидає помилку)
  • Треба повністю замінити значення? Тільки ref дозволяє це безпечно
  • Не знаєш що вибрати? Команда Vue зараз рекомендує ref як дефолт

Швидкий приклад

vue
<script setup> import { ref, reactive } from 'vue' const count = ref(0) // примітив: потрібен .value у скрипті count.value++ // count.value тепер 1 const state = reactive({ count: 0, user: 'Alice' }) // об'єкт: прямий доступ state.count++ // state.count тепер 1 </script> <template> <p>{{ count }}</p> <!-- автоматично розпаковується: показує 1, без .value --> <p>{{ state.count }}</p> <!-- прямий доступ: показує 1 --> </template>

Vue розпаковує ref у шаблонах автоматично. Пишеш {{ count }}, а не {{ count.value }}. У скрипті .value завжди потрібен.

Головна відмінність

ref додає тонку обгортку навколо будь-якого значення. Примітиви стають реактивними через цю обгортку, об'єкти отримують проксі всередині неї. Обгортка дає стабільне посилання, яке можна переназначити. reactive обгортки немає, тому отримуєш зручну крапкову нотацію на об'єктах, але замінити весь об'єкт не вийде. Переназначиш змінну з reactive і проксі зникне. Шаблон перестане оновлюватись.

Коли що використовувати

  • Лічильник, boolean, рядокref. Просто і можна переназначити.
  • Форма з вкладеними полямиreactive, якщо ніколи не замінюєш форму цілком; ref, якщо скидаєш через новий об'єкт.
  • Відповідь від APIref. Значення може бути null до запиту, а після - об'єктом або масивом.
  • Масив елементівref. Можеш замінити весь масив або додавати через push.
  • Не знаєш що вибрати? Багато команд використовують ref для всього і додають reactive тільки коли крапкова нотація реально спрощує код.

Порівняльна таблиця

Особливістьrefreactive
Вхідні типиПримітив або об'єктТільки об'єкт / масив
Доступ у скриптіПотрібен .valueПрямий доступ до властивостей
Доступ у шаблоніАвтоматично розпаковуєтьсяПрямий доступ до властивостей
Повне переназначенняТак (ref.value = newObj)Ні (ламає проксі)
ПримітивиТак (ref(42))Ні (кидає помилку)
ДеструктуризаціяБезпечна (ref залишається реактивним)Втрачає реактивність; потрібен toRefs
Тип у TypeScriptRef<T>Оригінальний тип
Коли братиЛічильники, динамічні значення, будь-який примітивОб'єкт фіксованої структури (якщо не потрібен повний reset)

Як це працює всередині

Обидва - ref і reactive - побудовані на ES6 Proxy. reactive обгортає об'єкт у Proxy напряму, перехоплюючи get і set на кожному вкладеному рівні. ref створює маленький об'єкт-обгортку з get/set на .value, який далі використовує ту саму систему проксі. Коли пишеш {{ count }} у шаблоні, компілятор Vue викликає unref(count): якщо у значення є прапорець __v_isRef, читається .value; інакше значення повертається як є.

Типові помилки

reactive на примітиві:

js
const count = reactive(0) // Помилка: reactive() must be called on an object

Рішення: const count = ref(0).

Забули .value у скрипті:

js
const count = ref(0) count++ // Неправильно: змінюється посилання на обгортку, а не внутрішнє значення // count.value залишається 0

Рішення: count.value++ або ++count.value.

Переназначення змінної з reactive:

js
let state = reactive({ count: 0 }) state = { count: 10 } // Проксі зник. Шаблон більше не оновлюється.

Рішення: мутуй властивості напряму (state.count = 10) або перейди на ref.

Деструктуризація reactive без toRefs:

js
const state = reactive({ name: 'Alice', age: 25 }) let { name } = state // name тепер звичайний рядок, не реактивний name = 'Bob' // шаблон не оновиться

Рішення: const { name } = toRefs(state), потім name.value = 'Bob'.

Де це зустрічається в реальних проектах

  • Pinia: часто використовує reactive для кореневого об'єкта стану (фіксована структура, прямі мутації)
  • Nuxt useFetch: повертає data як ref, бо до запиту значення null, а потім об'єкт або масив
  • VueUse: майже всі composables використовують ref всередині (useStorage, useLocalStorage тощо)
  • VeeValidate / FormKit: reactive для об'єктів форм з вкладеним станом полів
  • Стан компонента: на практиці більшість команд, що починають мішати обидва підходи, через якийсь час стандартизуються на ref - він покриває всі випадки без пастки з переназначенням

Додаткові питання

Q: Чому переназначення змінної з reactive ламає реактивність?
A: Тому що reactive() повертає Proxy, прив'язаний до конкретного об'єкта. Коли пишеш state = {}, ти відпускаєш посилання на цей Proxy. Трекер залежностей Vue все ще вказує на старий проксі, тому оновлення не спрацьовують.

Q: Як шаблон розпаковує ref автоматично?
A: Компілятор шаблонів Vue обгортає ref верхнього рівня через unref() у згенерованій render-функції. Якщо у значення є прапорець __v_isRef, читається .value; інакше значення повертається як є.

Q: Чи можна вкласти ref всередину reactive?
A: Так. reactive({ count: ref(0) }) працює, і Vue автоматично розпаковує вкладений ref, тому state.count доступний без .value. Але це ускладнює код - краще дотримуватись одного підходу.

Q: Що таке shallowRef і shallowReactive?
A: Обидва пропускають глибоку реактивність. shallowRef реагує тільки на переназначення .value, а не на зміни вкладених властивостей. shallowReactive відстежує тільки властивості верхнього рівня. Корисно для великих об'єктів, де глибоке відстеження занадто затратне.

Q: Є різниця в продуктивності для великих об'єктів?
A: Один reactive-проксі швидший за багато окремих ref-обгорток при тисячах реактивних властивостей. Різниця відчутна тільки при такому масштабі - у типовому стані компонента вона несуттєва.

Приклади

Базовий: лічильник і стан форми

vue
<script setup> import { ref, reactive } from 'vue' // ref для простого лічильника const count = ref(0) // reactive для форми (об'єкт тільки мутується, ніколи не замінюється цілком) const form = reactive({ name: '', email: '', preferences: { theme: 'light', notifications: true } }) function resetForm() { form.name = '' form.email = '' form.preferences.theme = 'light' // form = {} тут не спрацює - це видалить проксі } </script> <template> <button @click="count++">Кількість: {{ count }}</button> <form @submit.prevent> <input v-model="form.name" placeholder="Ім'я" /> <input v-model="form.email" placeholder="Email" /> <label> <input type="checkbox" v-model="form.preferences.notifications" /> Сповіщення </label> </form> <button @click="resetForm">Скинути</button> </template>

count використовує ref, бо це число з .value у скрипті. form використовує reactive, бо це об'єкт, який мутується на місці і ніколи не замінюється цілком.

Граничний випадок: переназначення і втрата реактивності

Це класика, яка плутає розробників на переході з Vue 2 або тих хто мішає обидва API в одному компоненті.

vue
<script setup> import { ref, reactive } from 'vue' const objRef = ref({ count: 0 }) const objReactive = reactive({ count: 0 }) // ref: переназначення .value створює новий проксі, реактивність збережена objRef.value = { count: 10 } console.log(objRef.value.count) // 10 - шаблон оновлюється коректно // reactive: переназначення змінної видаляє проксі повністю // objReactive = { count: 10 } // JS виконається, але Vue втрачає зв'язок // Безпечний спосіб оновити reactive - мутуй властивості Object.assign(objReactive, { count: 10 }) // Правильно console.log(objReactive.count) // 10 - шаблон все ще оновлюється </script>

ref контролює проксі через .value. reactive і є проксі. Заміниш змінну - проксі зникне.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?