Основи реактивності
Вподобання API
Ця сторінка та багато інших розділів гіду містять різний контент для опційного АРІ і композиційного АРІ. Вашим поточним налаштуванням є Композиційний АРІ. Ви можете перемикатися між стилями API за допомогою перемикача «Вподобання API» у верхній частині лівої бічної панелі.
Оголошення реактивного стану
Ми можемо створити реактивний об’єкт або масив за допомогою функції reactive()
:
js
import { reactive } from 'vue'
const state = reactive({ count: 0 })
Реактивні об’єкти є JavaScript проксі і поводяться так само, як звичайні об’єкти. Різниця полягає в тому, що Vue може відстежувати доступ до властивостей та мутацій реактивного об’єкта. Якщо вас цікавлять подробиці, ми пояснюємо, як працює реактивна система Vue в розділі Реактивність поглиблено, але ми рекомендуємо прочитати його після того, як ви закінчите вивченя основних розділів гіду.
Також до вашої уваги: Типізована reactive()
Щоб використовувати реактивний стан у шаблоні компонента, оголосіть та поверніть їх із функції setup()
компонента:
js
import { reactive } from 'vue'
export default {
// `setup` — це спеціальний хук, призначений для композиційного API.
setup() {
const state = reactive({ count: 0 })
// виділення стану до шаблону
return {
state
}
}
}
template
<div>{{ state.count }}</div>
Подібним чином ми можемо оголосити функції, які змінюють реактивний стан у тій самій області видимості та виділити їх як метод таким самим чином:
js
import { reactive } from 'vue'
export default {
setup() {
const state = reactive({ count: 0 })
function increment() {
state.count++
}
// не забудьте виділити функцію так само.
return {
state,
increment
}
}
}
Виділені методи зазвичай використовуються як слухачі подій:
template
<button @click="increment">
{{ state.count }}
</button>
<script setup>
Виділення стану та методів вручну за допомогою setup()
може бути надлишковим. На щастя, це необхідно лише тоді, коли не використовується крок збірки. При використанні однофайлових компонентів (SFC) ми можемо значно спростити використання за допомогою <script setup>
:
vue
<script setup>
import { reactive } from 'vue'
const state = reactive({ count: 0 })
function increment() {
state.count++
}
</script>
<template>
<button @click="increment">
{{ state.count }}
</button>
</template>
Імпорти верхнього рівня та змінні, оголошені в <script setup>
, автоматично доступні для використання в шаблоні того самого компонента.
У решті гіду ми будемо в основному використовувати синтаксис SFC +
<script setup>
для прикладів коду композиційного API, оскільки це найпоширеніше використання для розробників Vue.
Час оновлення DOM
Коли ви змінюєте реактивний стан, DOM оновлюється автоматично. Однак слід зазначити, що оновлення DOM не відбувається синхронно. Натомість Vue буферизує їх до «наступного тіку» в циклі оновлення, щоб гарантувати, що кожен компонент буде оновлено лише один раз, незалежно від того, скільки змін стану ви зробили.
Щоб дочекатися завершення оновлення DOM після зміни стану, ви можете скористатися nextTick() з глобального API:
js
import { nextTick } from 'vue'
function increment() {
state.count++
nextTick(() => {
// доступ до оновленої DOM
})
}
Глибока Реактивність
У Vue за замовчуванням стан є глибоко реактивним. Це означає, що ви можете очікувати, що зміни будуть виявлені, навіть коли ви змінюєте вкладені об’єкти або масиви:
js
import { reactive } from 'vue'
const obj = reactive({
nested: { count: 0 },
arr: ['foo', 'bar']
})
function mutateDeeply() {
// це працюватиме, як очікується
obj.nested.count++
obj.arr.push('baz')
}
Також можна явно створити неглибокі реактивні об’єкти, де реактивність існує лише на кореневому рівні, однак вони зазвичай потрібні лише в особливих випадках.
Реактивний проксі та оригінальний
Важливо зауважити, що значення, яке повертає reactive()
, є проксі оригінального об'єкту, який не є вихідним об'єктом:
js
const raw = {}
const proxy = reactive(raw)
// проксі не є вихідним об'єктом.
console.log(proxy === raw) // false
Реактивним є лише проксі – зміна вихідного об’єкта не призведе до оновлення. Тому найкраща практика під час роботи з реактивною системою Vue — використовувати виключно проксі-версії вашого стану.
Щоб забезпечити послідовний доступ до проксі, виклик reactive()
для того самого об’єкта завжди повертає той самий проксі, а виклик reactive()
для існуючого проксі також повертає той самий проксі:
js
// виклик reactive() для того самого об’єкта повертає той самий проксі
console.log(reactive(raw) === proxy) // true
// виклик reactive() для проксі повертає сам себе
console.log(reactive(proxy) === proxy) // true
Це правило також стосується вкладених об’єктів. Через глибоку реактивність вкладені об’єкти всередині реактивного об’єкта також є проксі:
js
const proxy = reactive({})
const raw = {}
proxy.nested = raw
console.log(proxy.nested === raw) // false
Обмеження reactive()
reactive()
API має два обмеження:
Він працює лише для типів об’єктів (об’єктів, масивів і типів колекцій, таких як
Map
іSet
). Він не може містити примітивні типи, такі якstring
,number
абоboolean
.Оскільки відстеження реактивності у Vue працює через доступ до властивостей, ми повинні завжди зберігати те саме посилання на реактивний об’єкт. Це означає, що ми не можемо просто «замінити» реактивний об’єкт, оскільки реактивний звязок також буде втрачено:
jslet state = reactive({ count: 0 }) // наведене вище посилання ({ count: 0 }) // більше не відстежується (реактивне з’єднання втрачено!) state = reactive({ count: 1 })
Це також означає, що коли ми призначаємо або деструктуруємо властивість реактивного об’єкта в локальні змінні, або коли ми передаємо цю властивість у функцію, ми втратимо реактивний зв’язок:
js
const state = reactive({ count: 0 })
// n — локальна змінна, яка не зʼєднана
// зі state.count.
let n = state.count
// не впливає на вихідний стан
n++
// count також від’єднано від state.count.
let { count } = state
// не впливає на вихідний стан
count++
// функція отримує просте число і
// не зможе відстежувати зміни в state.count
callSomeFunction(state.count)
Реактивні змінні використовуючи ref()
Щоб усунути обмеження reactive()
, Vue також надає функцію ref()
, яка дозволяє нам створювати реактивні "обʼєкти-референції", які можуть зберігати будь-який тип значень:
js
import { ref } from 'vue'
const count = ref(0)
ref()
приймає аргумент і повертає його загорнутим в об’єкт-рефернцію із властивістю .value
:
js
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
Також до вашої уваги: Типізована ref()
Подібно до властивостей реактивного об’єкта, властивість .value
є реактивною. Крім того, коли у ній зберігаються значення з типом об’єкта, обʼєкт-рефернція автоматично перетворює .value
за допомогою reactive()
.
Обʼєкт-рефернція, що містить значення об’єкта, може реактивно замінити весь об’єкт:
js
const objectRef = ref({ count: 0 })
// це працює реактивно
objectRef.value = { count: 1 }
Обʼєкт-рефернцію також можна передати у функції або деструктурувати з простих об’єктів без втрати реактивності:
js
const obj = {
foo: ref(1),
bar: ref(2)
}
// функція отримує обʼєкт-рефернцію
// їй потрібно отримати доступ до значення через .value, але це
// збереже зв'язок реактивності
callSomeFunction(obj.foo)
// все ще реактивний
const { foo, bar } = obj
Іншими словами, ref()
дозволяє нам створити "референцію" на будь-яке значення та передавати його без втрати реактивності. Ця можливість є досить важливою, оскільки вона часто використовується під час виділення логіки в Композиційні Функції.
"Розгортання" обєктів-референцій у шаблонах
Коли обєкти-референції доступні як властивості верхнього рівня в шаблоні, вони автоматично "розгортаються", тому немає необхідності використовувати .value
. Ось попередній приклад лічильника, в якому використовується ref()
:
vue
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">
{{ count }} <!-- .value не потрібна -->
</button>
</template>
Зауважте, що розгортання застосовується, лише якщо обєкт-референція є властивістю верхнього рівня в контексті візуалізації шаблону. Наприклад, object
є властивістю верхнього рівня, а object.foo
- ні.
Наприклад, в нас є такий обʼєкт:
js
const object = { foo: ref(1) }
Наступний вираз НЕ працюватиме належним чином:
template
{{ object.foo + 1 }}
Відображеним результатом буде [object Object]1
, тому що object.foo
є об'єктом-референцією. Ми можемо виправити це, зробивши foo
властивістю верхнього рівня:
js
const { foo } = object
template
{{ foo + 1 }}
Тепер результат буде «2».
Одна річ, яку слід зазначити, полягає в тому, що обʼєкт-референцію також буде розгорнуто, якщо це остаточне обчислене значення текстової інтерполяції (наприклад тег {{ }}
), тому наступне буде відображати 1
:
template
{{ object.foo }}
Це лише зручна функція інтерполяції тексту яка еквівалентна {{ object.foo.value }}
.
"Розгортання" обєктів-референцій у реактивних обʼєктах
Коли ref
використовується або змінюється як властивість реактивного об’єкта, він також автоматично розгортається, тому поводиться як звичайна властивість:
js
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1
Якщо новий обʼєкт-референція призначається властивості існуючого обʼєкта-референції, то, він замінить старий обʼєкт-рефренцію:
js
const otherCount = ref(2)
state.count = otherCount
console.log(state.count) // 2
// вихідний обʼєкт-референцію тепер відʼєднано від state.count
console.log(count.value) // 1
Розгортання обєктів-референцій відбувається лише тоді, коли вони вкладені в глибоко реактивний об’єкт. Це не відбувається, коли до них звертаються як до властивості неглибоко реактивного обʼєкту.
"Розгортання" обєктів-референцій у масивах та колекціях
На відміну від реактивних об’єктів, розгортання не виконується, коли обєкта-референція використовується як елемент реактивного масиву або нативного типу колекції, наприклад значення з типом Map
:
js
const books = reactive([ref('Vue 3 Guide')])
// потрібно використовувати .value
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// потрібно використовувати .value
console.log(map.get('count').value)