Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Ref проти reactive у Vue.js». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**`ref` проти `reactive`** у Vue.js: `ref` обгортає будь-яке значення в реактивний контейнер з доступом через `.value` у скрипті; `reactive` створює проксі об'єкта без обгортки. ```js const count = ref(0); count.value++ // .value обов'язковий у скрипті const state = reactive({ count: 0 }); state.count++ // прямий доступ ``` **Ключове:** `ref` приймає примітиви і дозволяє повне переназначення; `reactive` тільки для об'єктів і втрачає реактивність при переназначенні змінної. Команда Vue рекомендує `ref` як дефолт.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**`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`, якщо скидаєш через новий об'єкт. - **Відповідь від API** → `ref`. Значення може бути `null` до запиту, а після - об'єктом або масивом. - **Масив елементів** → `ref`. Можеш замінити весь масив або додавати через `push`. - **Не знаєш що вибрати?** Багато команд використовують `ref` для всього і додають `reactive` тільки коли крапкова нотація реально спрощує код. ### Порівняльна таблиця | Особливість | `ref` | `reactive` | |---|---|---| | Вхідні типи | Примітив або об'єкт | Тільки об'єкт / масив | | Доступ у скрипті | Потрібен `.value` | Прямий доступ до властивостей | | Доступ у шаблоні | Автоматично розпаковується | Прямий доступ до властивостей | | Повне переназначення | Так (`ref.value = newObj`) | Ні (ламає проксі) | | Примітиви | Так (`ref(42)`) | Ні (кидає помилку) | | Деструктуризація | Безпечна (ref залишається реактивним) | Втрачає реактивність; потрібен `toRefs` | | Тип у TypeScript | `Ref<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` і є проксі. Заміниш змінну - проксі зникне.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.