Основи реактивності
Вподобання API
Ця сторінка та багато інших розділів гіду містять різний контент для опційного АРІ і композиційного АРІ. Вашим поточним налаштуванням є Композиційний АРІ. Ви можете перемикатися між стилями API за допомогою перемикача «Вподобання API» у верхній частині лівої бічної панелі.
Оголошення реактивного стану
ref()
В Composition API рекомендований спосіб оголосити реактивний стан — це використовувати функцію ref()
:
js
import { ref } from 'vue'
const count = ref(0)
ref()
приймає аргумент і повертає його в об’єкті ref із властивістю .value
:
js
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
Також до вашої уваги: Типізація референцій
Щоб використовувати реактивний стан у шаблоні компонента, оголосіть та поверніть їх із функції setup()
компонента:
js
import { ref } from 'vue'
export default {
// `setup` — це спеціальний хук, призначений для композиційного API.
setup() {
const count = ref(0)
// виділення стану до шаблону
return {
count
}
}
}
template
<div>{{ count }}</div>
Зверніть увагу, що нам не потрібно було додавати .value
під час використання посилання в шаблоні. Для зручності посилання автоматично розгортаються під час використання всередині шаблонів (з кількома застереженнями).
Ви також можете змінити посилання безпосередньо в обробниках подій:
template
<button @click="count++">
{{ count }}
</button>
Для більш складної логіки ми можемо оголосити функції, які змінюють посилання в тій самій області видимості та виставити їх як методи поряд зі станом:
js
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
function increment() {
// .value is needed in JavaScript
count.value++
}
// не забудьте виділити функцію так само.
return {
count,
increment
}
}
}
Виділені методи зазвичай використовуються як слухачі подій:
template
<button @click="increment">
{{ count }}
</button>
Ось приклад, опублікований на Codepen, без використання інструментів збірки.
<script setup>
Розкриття стану та методів вручну за допомогою setup()
може бути заскладним. На щастя, цього можна уникнути, використовуючи Однофайлові компоненти (SFC). Ми можемо спростити використання за допомогою <script setup>
:
vue
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">
{{ count }}
</button>
</template>
Імпорт верхнього рівня, змінні та функції, оголошені в <script setup>
, автоматично придатні для використання в шаблоні того самого компонента. Подумайте про шаблон як про функцію JavaScript, оголошену в тій самій області видимості – вона природно має доступ до всього, що оголошено поруч із нею.
TIP
У решті посібника ми будемо в основному використовувати синтаксис SFC + <script setup>
для прикладів коду Composition API, оскільки це найпоширеніше використання для розробників Vue.
Якщо ви не використовуєте SFC, ви можете використовувати Composition API за допомогою параметра setup()
.
Чому референції?
Вам може бути цікаво, навіщо нам потрібні посилання з .value
замість простих змінних. Щоб пояснити це, нам потрібно буде коротко обговорити, як працює система реактивності Vue.
Коли ви використовуєте посилання в шаблоні та змінюєте значення посилання пізніше, Vue автоматично виявляє зміни та відповідно оновлює DOM. Це стало можливим завдяки системі реактивності на основі відстеження залежностей. Коли компонент рендериться вперше, Vue відстежує кожне посилання, яке було використано під час рендерингу. Пізніше, коли посилання мутується, це ініціює повторний рендеринг для компонентів, які його відстежують.
У стандартному JavaScript немає способу виявити доступ або мутацію простих змінних. Однак ми можемо перехопити операції отримання та встановлення властивостей об’єкта за допомогою методів getter та setter.
Властивість .value
дає Vue можливість виявити, коли посилання було доступне або змінене. Під капотом Vue виконує відстеження у своєму геттері та виконує запуск у своєму сеттері. Концептуально ви можете думати про посилання як про об’єкт, який виглядає так:
js
// pseudo code, not actual implementation
const myRef = {
_value: 0,
get value() {
track()
return this._value
},
set value(newValue) {
this._value = newValue
trigger()
}
}
Ще одна приємна риса посилань полягає в тому, що на відміну від простих змінних, ви можете передавати посилання у функції, зберігаючи доступ до останнього значення та зв’язку реактивності. Це особливо корисно під час рефакторингу складної логіки в багаторазовий код.
Система реактивності обговорюється більш детально в розділі Реактивність поглиблено.
Глибока реактивність
Посилання можуть містити будь-які типи значень, у тому числі глибоко вкладені об’єкти, масиви або вбудовані структури даних JavaScript, такі як Map
.
Посилання зробить його значення глибоко реактивним. Це означає, що ви можете очікувати, що зміни будуть виявлені, навіть коли ви змінюєте вкладені об’єкти або масиви:
js
import { ref } from 'vue'
const obj = ref({
nested: { count: 0 },
arr: ['foo', 'bar']
})
function mutateDeeply() {
// це працюватиме, як очікується
obj.value.nested.count++
obj.value.arr.push('baz')
}
Непримітивні значення перетворюються на реактивні проксі через reactive()
, який обговорюється нижче.
Також можна відмовитися від глибокої реактивності за допомогою shallow refs. Для неглибоких посилань реактивність відстежується доступ лише до .value
. Неглибокі посилання можна використовувати для оптимізації продуктивності, уникаючи витрат на спостереження за великими об’єктами, або у випадках, коли внутрішнім станом керує зовнішня бібліотека.
Подальше читання:
Час оновлення DOM
Коли ви змінюєте реактивний стан, DOM оновлюється автоматично. Однак слід зазначити, що оновлення DOM не відбувається синхронно. Натомість Vue буферизує їх до «наступного тіку» в циклі оновлення, щоб гарантувати, що кожен компонент буде оновлено лише один раз, незалежно від того, скільки змін стану ви зробили.
Щоб дочекатися завершення оновлення DOM після зміни стану, ви можете скористатися nextTick() з глобального API:
js
import { nextTick } from 'vue'
async function increment() {
count.value++
await nextTick()
// DOM оновлено
}
reactive()
Існує інший спосіб оголосити реактивний стан за допомогою API reactive()
. На відміну від референції, яка загортає внутрішнє значення в спеціальний об’єкт, reactive()
робить сам об’єкт реактивним:
js
import { reactive } from 'vue'
const state = reactive({ count: 0 })
Дивіться також: Типізація реактивності
Використання в шаблоні:
template
<button @click="state.count++">
{{ state.count }}
</button>
Реактивні об'єкти є проксі-серверами JavaScript і поводяться так само, як звичайні об'єкти. Різниця полягає в тому, що Vue здатний перехоплювати доступ і мутацію всіх властивостей реактивного об'єкта для відстеження реактивності та запуску.
reactive()
глибоко перетворює об'єкт: вкладені об'єкти також загортаються за допомогою reactive()
під час доступу. Він також викликається ref()
внутрішньо, коли значення ref є об'єктом. Подібно до неглибоких посилань, існує також shallowReactive()
API для відмови від глибокої реактивності.
Реактивний проксі проти оригінального
Важливо зауважити, що значення, яке повертає 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 })
// count від'єднується від state.count під час деструктурування.
let { count } = state
// does not affect original state
count++
// функція отримує звичайне число і
// не зможе відстежувати зміни в state.count
// ми повинні передати весь об'єкт, щоб зберегти реактивність
callSomeFunction(state.count)
Через ці обмеження ми рекомендуємо використовувати ref() як основний API для оголошення реактивного стану.
Додаткові відомості про розгортання референцій
Як властивість реактивного об'єкта
Референція автоматично розгортається під час доступу або змінюється як властивість реактивного об'єкта. Іншими словами, вона поводиться як звичайна властивість:
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)
Застереження під час розгортання в шаблонах
Розгортання посилання в шаблонах застосовується, лише якщо посилання є властивістю верхнього рівня в контексті відтворення шаблону.
У наведеному нижче прикладі count
і object
є властивостями верхнього рівня, а object.id
- ні:
js
const count = ref(0)
const object = { id: ref(1) }
Отже, цей вираз працює, як очікувалося:
template
{{ count + 1 }}
...а цей НІ:
template
{{ object.id + 1 }}
Відображеним результатом буде [object Object]1
, тому що object.id
не розгортається під час обчислення виразу та залишається об'єктом посилання. Щоб виправити це, ми можемо деструктурувати id
у властивість верхнього рівня:
js
const { id } = object
template
{{ id + 1 }}
Тепер результат візуалізації буде 2
.
Інша річ, на яку слід звернути увагу, полягає в тому, що посилання розгортається, якщо воно є остаточним оціненим значенням текстової інтерполяції (тобто тегом {{ }}
), тому наступне відобразить 1
:
template
{{ object.id }}
Це лише зручна функція інтерполяції тексту та еквівалентна {{ object.id.value }}
.