Podstawy reaktywności
API Preference
Ta strona i wiele innych rozdziałów w dalszej części przewodnika zawiera różne treści dla Options API i Composition API. Twoim obecnym wyborem jest Composition API. Możesz przełączać się między stylami API za pomocą przełączników „Preferowane API” u góry lewego paska bocznego.
Deklarowanie reaktywnego stanu
ref()
W API Kompozycji zalecanym sposobem deklarowania reaktywnego stanu jest użycie funkcji ref()
:
js
import { ref } from 'vue'
const count = ref(0)
ref()
przyjmuje argument i zwraca go opakowanego w obiekt z właściwością .value
:
js
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
Zobacz także: Typowanie odniesień (ref)
Aby uzyskać dostęp do ref w szablonie komponentu, należy je zadeklarować i zwrócić z funkcji setup()
:
js
import { ref } from 'vue'
export default {
// `setup` to specjalny hak dedykowany Composition API.
setup() {
const count = ref(0)
// udostępnij odniesienie (ref) do szablonu
return {
count
}
}
}
template
<div>{{ count }}</div>
Zauważ, że w szablonie nie musieliśmy używać .value
. Dla wygody, Vue automatycznie rozpakowuje ref w szablonach (z kilkoma zastrzeżeniami).
Możesz także bezpośrednio modyfikować ref w obsłudze zdarzeń:
template
<button @click="count++">
{{ count }}
</button>
W przypadku bardziej złożonej logiki można deklarować funkcje, które modyfikują ref w tym samym zakresie i zwracać je jako metody:
js
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
function increment() {
// użycie .value jest potrzebne JavaScript
count.value++
}
// użycie .value jest potrzebne JavaScript
return {
count,
increment
}
}
}
Udostępnione metody można następnie wykorzystać jako procedury obsługi zdarzeń:
template
<button @click="increment">
{{ count }}
</button>
Oto przykład na żywo w Codepen, bez użycia żadnych narzędzi do kompilacji.
<script setup>
Ręczne ujawnianie stanu i metod za pomocą setup()
może być rozwlekłe. Na szczęście można tego uniknąć, używając Single-File Components (SFCs). Możemy uprościć użycie za pomocą <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>
Importy najwyższego poziomu, zmienne i funkcje zadeklarowane w <script setup>
są automatycznie używalne w szablonie tego samego komponentu. Pomyśl o szablonie jako o funkcji JavaScript zadeklarowanej w tym samym zakresie - naturalnie ma dostęp do wszystkiego, co jest zadeklarowane obok niej.
TIP
W dalszej części przewodnika będziemy używać głównie składni SFC + <script setup>
dla przykładów kodu Composition API, ponieważ jest to najczęstsze użycie przez programistów Vue.
Jeśli nie używasz SFC, nadal możesz używać Composition API z opcją setup()
Dlaczego używamy refs?
Możesz się zastanawiać, dlaczego potrzebujemy refs z .value
, a nie po prostu zwykłych zmiennych. Aby to wyjaśnić, musimy krótko omówić, jak działa system reaktywności w Vue.
Kiedy używasz ref w szablonie i później zmieniasz wartość tego ref, Vue automatycznie wykrywa tę zmianę i aktualizuje DOM. Jest to możliwe dzięki systemowi reaktywności opartemu na śledzeniu zależności. Kiedy komponent jest renderowany po raz pierwszy, Vue śledzi każdy ref, który został użyty podczas renderowania. Później, gdy ref zostaje zmodyfikowany, wywołuje to ponowne renderowanie komponentów, które go śledzą.
W standardowym JavaScript nie ma sposobu na wykrycie dostępu lub modyfikacji zwykłych zmiennych. Jednak możemy przechwycić operacje get i set na właściwościach obiektów za pomocą metod getter i setter.
Właściwość .value
daje Vue możliwość wykrycia, kiedy ref został użyty lub zmodyfikowany. Wewnątrz Vue odbywa się śledzenie w jego getterze, a wywołanie ponownego renderowania w jego setterze. Koncepcyjnie, możesz myśleć o ref jako o obiekcie, który wygląda tak:
js
// pseudokod, nie jest to rzeczywista implementacja
const myRef = {
_value: 0,
get value() {
track()
return this._value
},
set value(newValue) {
this._value = newValue
trigger()
}
}
Inną zaletą refs jest to, że w przeciwieństwie do zwykłych zmiennych, możesz przekazywać refs do funkcji, zachowując dostęp do najnowszej wartości i połączenie reaktywności. Jest to szczególnie przydatne, gdy refaktoryzujesz złożoną logikę kodu, który może być wielokrotnie używany.
System reaktywności jest omawiany szczegółowo w sekcji Reaktywność w szczegółach.
Głęboka reaktywność
Refs mogą przechowywać dowolny typ wartości, w tym głęboko zagnieżdżone obiekty, tablice lub wbudowane struktury danych JavaScript, takie jak Map
.
Ref sprawi, że jego wartość będzie głęboko reaktywna. Oznacza to, że możesz oczekiwać, że zmiany będą wykrywane, nawet gdy modyfikujesz zagnieżdżone obiekty lub tablice:
js
import { ref } from 'vue'
const obj = ref({
nested: { count: 0 },
arr: ['foo', 'bar']
})
function mutateDeeply() {
// te operacje będą działać zgodnie z oczekiwaniami.
obj.value.nested.count++
obj.value.arr.push('baz')
}
Nieprymitywne wartości są zamieniane na reaktywne proxy za pomocą reactive()
, które omawiamy poniżej.
Możliwe jest również wyłączenie głębokiej reaktywności za pomocą płytkich odniesień(shallow refs). Shallow refs śledzą tylko dostęp do .value
w reaktywności. Płytkie refs mogą być używane do optymalizacji wydajności, unikając kosztu obserwacji dużych obiektów lub w przypadkach, gdy wewnętrzny stan jest zarządzany przez zewnętrzną bibliotekę.
Dalsza lektura:
Czas aktualizacji DOM
Kiedy modyfikujesz reaktywny stan, DOM jest automatycznie aktualizowany. Należy jednak zauważyć, że aktualizacje DOM nie są stosowane synchronicznie. Zamiast tego Vue buforuje je do „następnej klatki (next tick)” w cyklu aktualizacji, aby upewnić się, że każdy komponent jest aktualizowany tylko raz, niezależnie od tego, ile zmian stanu zostało dokonanych.
Aby poczekać na zakończenie aktualizacji DOM po zmianie stanu, możesz użyć globalnego API nextTick():
js
import { nextTick } from 'vue'
async function increment() {
count.value++
await nextTick()
// Teraz DOM jest aktualizowany
}
reactive()
Istnieje inny sposób deklarowania reaktywnego stanu za pomocą API reactive()
. W przeciwieństwie do ref, które opakowuje wewnętrzną wartość w specjalny obiekt, reactive()
sprawia, że obiekt sam w sobie staje się reaktywny:
js
import { reactive } from 'vue'
const state = reactive({ count: 0 })
Zobacz również: Typowanie reactive
Usage in template:
template
<button @click="state.count++">
{{ state.count }}
</button>
Reaktywne obiekty to JavaScript Proxies i zachowują się jak zwykłe obiekty. Różnica polega na tym, że Vue jest w stanie przechwycić dostęp i modyfikację wszystkich właściwości reaktywnego obiektu w celu śledzenia reaktywności i wywoływania aktualizacji.
reactive()
konwertuje obiekt głęboko: zagnieżdżone obiekty są również opakowywane wreactive()
, gdy są dostępne. Jest również wywoływane wewnętrznie przez ref()
, gdy wartość ref jest obiektem. Podobnie jak w przypadku płytkich odniesień (shallow refs), istnieje również API shallowReactive()
do rezygnacji z głębokiej reaktywności.
Proxy reaktywne vs. oryginalne
Ważne jest, aby zauważyć, że zwrócony przez reactive()
obiekt to Proxy oryginalnego obiektu, który nie jest równy oryginalnemu obiektowi:
js
const raw = {}
const proxy = reactive(raw)
// proxy NIE jest równe oryginałowi.
console.log(proxy === raw) // false
Tylko proxy jest reaktywne - modyfikowanie oryginalnego obiektu nie wywoła aktualizacji. Dlatego najlepszą praktyką podczas pracy z systemem reaktywności Vue jest wyłącznie używanie wersji proxy twojego stanu.
Aby zapewnić spójny dostęp do proxy, wywołanie reactive()
na tym samym obiekcie zawsze zwraca to samo proxy, a wywołanie reactive()
na istniejącym proxy również zwraca to samo proxy:
js
// wywołanie reactive() na tym samym obiekcie zwraca to samo proxy
console.log(reactive(raw) === proxy) // true
// wywołanie reactive() na proxy zwraca samo proxy
console.log(reactive(proxy) === proxy) // true
Ta zasada dotyczy również obiektów zagnieżdżonych. Ze względu na głęboką reaktywność, obiekty zagnieżdżone wewnątrz obiektu reaktywnego są również proxy:
js
const proxy = reactive({})
const raw = {}
proxy.nested = raw
console.log(proxy.nested === raw) // false
Ograniczenia reactive()
API reactive()
ma kilka ograniczeń:
Ograniczone typy wartości: działa tylko dla typów obiektów (obiekty, tablice oraz typy kolekcji takie jak
Map
iSet
). Nie obsługuje typów prymitywnych jakstring
,number
czyboolean
.Nie można zastąpić całego obiektu: ponieważ śledzenie reaktywności Vue działa na dostępach do właściwości, musimy zawsze zachować tę samą referencję do reaktywnego obiektu. Oznacza to, że nie możemy łatwo „zastąpić” reaktywnego obiektu, ponieważ połączenie reaktywności z pierwszą referencją jest tracone:
jslet state = reactive({ count: 0 }) // powyższa referencja ({ count: 0 }) nie jest już śledzona // (połączenie reaktywności jest utracone!) state = reactive({ count: 1 })
Nieprzyjazne destrukturyzacji: gdy destrukturalizujemy właściwość reaktywnego obiektu typu prymitywnego do lokalnych zmiennych lub przekazujemy tę właściwość do funkcji, tracimy połączenie reaktywności:
jsconst state = reactive({ count: 0 }) // count jest odłączony od state.count, gdy jest destrukturyzowane. let { count } = state // nie wpływa na oryginalny stan count++ // funkcja otrzymuje zwykłą liczbę // i nie będzie w stanie śledzić zmian w state.count // musimy przekazać cały obiekt, aby zachować reaktywność callSomeFunction(state.count)
Z powodu tych ograniczeń zalecamy używanie ref()
jako głównego API do deklarowania reaktywnego stanu.
Dodatkowe szczegóły rozpakowywania ref
Jako właściwość obiektu reaktywnego
Ref jest automatycznie rozpakowywany, gdy jest używany jako właściwość obiektu reaktywnego. Innymi słowy, zachowuje się jak zwykła właściwość:
js
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1
Jeśli nowy ref zostanie przypisany do właściwości powiązanej z istniejącym ref, zastąpi stary ref:
js
const otherCount = ref(2)
state.count = otherCount
console.log(state.count) // 2
// oryginalny ref jest teraz odłączony od state.count
console.log(count.value) // 1
Rozpakowywanie ref zachodzi tylko wtedy, gdy jest zagnieżdżony w głęboko reaktywnym obiekcie. Nie ma to miejsca, gdy jest dostępny jako właściwość płytkiego obiektu reaktywnego.
Ostrzeżenie dotyczące tablic i kolekcji
W przeciwieństwie do obiektów reaktywnych, nie wykonuje się rozpakowywania, gdy odwołanie jest uzyskiwane jako element tablicy reaktywnej lub natywnego typu kolekcji, takiego jak Map
:
js
const books = reactive([ref('Vue 3 Guide')])
// tutaj potrzebne jest .value
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// tutaj potrzebne jest .value
console.log(map.get('count').value)
Ostrzeżenie dotyczące rozpakowywania w szablonach
Rozpakowywanie ref w szablonach ma zastosowanie tylko wtedy, gdy ref jest właściwością najwyższego poziomu w kontekście renderowania szablonu.
W poniższym przykładzie count
i object
są właściwościami najwyższego poziomu, ale object.id
nie jest:
js
const count = ref(0)
const object = { id: ref(1) }
Zatem wyrażenie to działa zgodnie z oczekiwaniami:
template
{{ count + 1 }}
...podczas gdy ten NIE:
template
{{ object.id + 1 }}
Wyrenderowany wynik będzie [object Object]1
, ponieważ object.id
nie jest rozpakowywany podczas oceny wyrażenia i pozostaje obiektem ref. Aby to naprawić, możemy zdestrukturyzować id
do właściwości najwyższego poziomu:
js
const { id } = object
template
{{ id + 1 }}
Teraz wynik renderowania będzie wynosić 2
.
Another thing to note is that a ref does get unwrapped if it is the final evaluated value of a text interpolation (i.e. a {{ }}
tag), so the following will render 1
:
Inną rzeczą, na którą należy zwrócić uwagę, jest to, że ref jest rozpakowywany, jeśli jest ostateczną wartością interpolacji tekstu (tj. znacznikiem {{ }}
), więc poniższe wyrażenie spowoduje wyrenderowanie 1
:
template
{{ object.id }}
Jest to po prostu wygodna funkcja interpolacji tekstu i jest równoważna z {{ object.id.value }}
.