Podstawy reaktywności
Jobs by vuejobs.com
Preferencje API
Ta strona i wiele innych rozdziałów w dalszej części podręcznika zawierają różne treści dla interfejsów Options API i Composition API. Obecne twoje preferencje to Composition API. Możesz przełączać się między stylami API za pomocą przełączników "Preferowane API" znajdujących się w górnej części lewego paska bocznego.
Deklarowanie Stanu Reaktywnego
Możemy utworzyć reaktywny obiekt lub tablicę za pomocą funkcji reactive()
:
js
import { reactive } from 'vue'
const state = reactive({ count: 0 })
Obiekty reaktywne są JavaScript Proxies i zachowują się tak samo jak normalne obiekty. Różnica polega na tym, że Vue jest w stanie śledzić dostęp do właściwości i mutacje obiektu reaktywnego. Jeśli jesteś ciekaw szczegółów, wyjaśniamy, jak działa system reaktywności w Vue w Reactivity in Depth - zalecamy jednak przeczytanie go po ukończeniu głównego przewodnika.
Zobacz także: Typowanie Reaktywne
Aby użyć stanu reaktywnego w szablonie komponentu, należy go zadeklarować i zwrócić w funkcji setup()
komponentu:
js
import { reactive } from 'vue'
export default {
// `setup` jest specjalnym hookiem przeznaczonym dla Composition API.
setup() {
const state = reactive({ count: 0 })
// udostępnij stan szablonowi
return {
state
}
}
}
template
<div>{{ state.count }}</div>
Podobnie, możemy zadeklarować funkcje, które mutują stan reaktywny w tym samym zakresie i udostępnić je jako metody obok stanu:
js
import { reactive } from 'vue'
export default {
setup() {
const state = reactive({ count: 0 })
function increment() {
state.count++
}
// nie zapomnij również udostępnić tej funkcji.
return {
state,
increment
}
}
}
Metody jawne są zwykle używane jako nasłuchy zdarzeń (listeners):
template
<button @click="increment">
{{ state.count }}
</button>
<script setup>
Ręczne udostępnianie stanu i metod przez setup()
może być czasochłonne. Na szczęście, jest to konieczne tylko wtedy, gdy nie używamy proccesu generowania kodu. Kiedy używamy komponentów jednoplikowych (SFC), możemy znacznie uprościć ich użycie za pomocą <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>
Importy i zmienne najwyższego poziomu zadeklarowane w <script setup>
są automatycznie wykorzystywane w szablonie tego samego komponentu.
W dalszej części poradnika będziemy przede wszystkim używać składni SFC +
<script setup>
w przykładach kodu Composition API , ponieważ jest to najczęściej spotykane użycie przez programistów Vue.
Czas aktualizacji DOM
Gdy użytkownik modyfikuje stan reaktywny, DOM jest aktualizowany automatycznie. Należy jednak zauważyć, że aktualizacje DOM nie są stosowane synchronicznie. Zamiast tego, Vue buforuje je do "następnego kliknięcia" w cyklu aktualizacji, aby zapewnić, że każdy komponent musi zostać zaktualizowany tylko raz, bez względu na to, ile zmian stanu zostało dokonanych.
Aby poczekać na zakończenie aktualizacji DOM po zmianie stanu, można użyć globalnego API nextTick():
js
import { nextTick } from 'vue'
function increment() {
state.count++
nextTick(() => {
// dostęp do zaktualizowanego DOM
})
}
Dogłębna reaktywność
W Vue stan jest domyślnie głęboko reaktywny. Oznacza to, że można oczekiwać, że zmiany zostaną wykryte nawet wtedy, gdy zmutowane zostaną zagnieżdżone obiekty lub tablice:
js
import { reactive } from 'vue'
const obj = reactive({
nested: { count: 0 },
arr: ['foo', 'bar']
})
function mutateDeeply() {
// these will work as expected.
obj.nested.count++
obj.arr.push('baz')
}
Możliwe jest również jawne tworzenie [płytkich obiektów reaktywnych] (/api/reactivity-advanced.html#shallowreactive), w których reaktywność jest śledzona tylko na poziomie głównym, jednak są one zazwyczaj potrzebne tylko w zaawansowanych przypadkach użycia.
Reaktywne proxy vs. Oryginał
Należy zauważyć, że wartość zwracana przez reactive()
jest 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 - mutowanie oryginalnego obiektu nie spowoduje aktualizacji. Dlatego najlepszą praktyką podczas pracy z systemem reaktywności Vue jest wyłączne używanie proxy.
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 funkcji reactive() na tym samym obiekcie zwraca to samo proxy
console.log(reactive(raw) === proxy) // true
// wywołanie funkcji reactive() na proxy zwraca samo siebie
console.log(reactive(proxy) === proxy) // true
Ta zasada dotyczy także 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 dwa ograniczenia:
Działa tylko dla typów obiektowych (obiekty, tablice i typy kolekcji takie jak
Map
iZestaw
). Nie może obsługiwać typów prymitywnych takich jakstring
,number
czyboolean
.Ponieważ śledzenie reaktywności w Vue działa poprzez dostęp do właściwości, musimy zawsze utrzymywać tę samą referencję do obiektu reaktywnego. Oznacza to, że nie możemy łatwo "zastąpić" obiektu reaktywnego, ponieważ połączenie reaktywności z pierwszym odniesieniem zostaje utracone:
jslet state = reactive({ count: 0 }) // powyższe odniesienie ({ count: 0 }) nie jest już śledzone (utracono połączenie reaktywne!) state = reactive({ count: 1 })
Oznacza to również, że gdy przypiszemy lub przekształcimy właściwość obiektu reaktywnego do zmiennych lokalnych lub gdy przekażemy tę właściwość do funkcji, utracimy połączenie z reaktywnością:
jsconst state = reactive({ count: 0 }) // n jest zmienną lokalną, która jest odłączona // od state.count. let n = state.count // nie wpływa na stan początkowy n++ // count jest również odłączone od state.count. let { count } = state // nie ma wpływu na stan początkowy count++ // funkcja otrzymuje zwykłą liczbę i // nie będzie w stanie śledzić zmian w state.count callSomeFunction(state.count)
Reactive Variables with ref()
Aby zaradzić ograniczeniom funkcji reactive()
, Vue dostarcza również funkcję ref()
, która pozwala nam na tworzenie reaktywnych "refów ", które mogą przechowywać dowolny typ wartości:
js
import { ref } from 'vue'
const count = ref(0)
Polecenie ref()
pobiera argument i zwraca go opakowany w obiekt ref 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 refów
Podobnie jak właściwości obiektu reaktywnego, właściwość .value
ref jest reaktywna. Dodatkowo, gdy przechowuje typy obiektów, ref automatycznie konwertuje swoją .value
za pomocą reactive()
.
Ref zawierający wartość obiektu może reaktywnie zastąpić cały obiekt:
js
const objectRef = ref({ count: 0 })
// działa to reaktywnie
objectRef.value = { count: 1 }
Refy można również przekazywać do funkcji lub usuwać z obiektów bez utraty reaktywności:
js
const obj = {
foo: ref(1),
bar: ref(2)
}
// funkcja otrzymuje ref
// musi uzyskać dostęp do wartości za pomocą .value, ale
// zachowa połączenie reaktywne
callSomeFunction(obj.foo)
// wciąż reaktywne
const { foo, bar } = obj
Innymi słowy, ref()
pozwala nam stworzyć "referencję" do dowolnej wartości i przekazać ją dalej bez utraty reaktywności. Ta zdolność jest dość ważna, ponieważ jest często używana podczas wyciągania logiki do Composable Functions.
Rozwijanie refów w szablonach
Gdy refy są dostępne jako właściwości najwyższego poziomu w szablonie, są automatycznie "rozpakowywane", więc nie ma potrzeby używania .value
. Oto poprzedni przykład z licznikiem, używający zamiast tego ref()
:
vue
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">
{{ count }} <!-- nie potrzeba .value -->
</button>
</template>
Zwróć uwagę, że rozpakowywanie ma zastosowanie tylko wtedy, gdy ref jest właściwością najwyższego poziomu w kontekście renderowania szablonu. Na przykład, foo
jest właściwością najwyższego poziomu, ale object.foo
nie jest.
Zatem biorąc pod uwagę następujący obiekt:
js
const object = { foo: ref(1) }
Poniższe wyrażenie NIE będzie działać zgodnie z oczekiwaniami:
template
{{ object.foo + 1 }}
Wynikiem renderowania będzie [object Object]
, ponieważ object.foo
jest obiektem ref. Możemy to naprawić przez uczynienie foo
właściwością najwyższego poziomu:
js
const { foo } = object
template
{{ foo + 1 }}
Teraz wynikiem renderowania będzie 2
.
Teraz wynikiem renderowania będzie 2
. Należy zauważyć, że ref zostanie również zawinięty, jeśli jest końcową wartością interpolacji tekstowej (np. znacznik {{ }}
), więc poniższy wynik wyrenderuje 1
:
template
{{ object.foo }}
Jest to tylko wygodna cecha interpolacji tekstu i jest równoważna {{ object.foo.value }}
.
Rozpakowywanie refów w obiektach reaktywnych
Kiedy uzyskuje się dostęp do ref
lub modyfikuje się go jako właściwość obiektu reaktywnego, jest on również automatycznie rozwijany, więc zachowuje się jak normalna 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 do właściwości powiązanej z istniejącym ref zostanie przypisany nowy ref, zastąpi on 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
Rozwijanie refów ma miejsce tylko wtedy, gdy są one zagnieżdżone wewnątrz głębokiego obiektu reaktywnego. Nie ma zastosowania, gdy jest dostępna jako właściwość [płytkiego obiektu reaktywnego] (/api/reactivity-advanced.html#shallowreactive).
Rozwijanie ref w tablicach i kolekcjach
W przeciwieństwie do obiektów reaktywnych, nie ma rozwijania, gdy ref jest dostępny jako element tablicy reaktywnej lub natywnej kolekcji typu Map
:
js
const books = reactive([ref('Vue 3 Guide')])
// tutaj musi być .value
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// tutaj musi być .value
console.log(map.get('count').value)
Przekształcenie reaktywności
Konieczność używania .value
z referencjami jest wadą narzuconą przez ograniczenia języka JavaScript. Jednak dzięki przekształceniom w czasie kompilacji możemy poprawić ergonomię poprzez automatyczne dołączanie .value
w odpowiednich miejscach. Vue udostępnia transformację w czasie kompilacji, która pozwala nam napisać wcześniejszy przykład "licznika" w ten sposób:
vue
<script setup>
let count = $ref(0)
function increment() {
// nie trzeba używać .value
count++
}
</script>
<template>
<button @click="increment">{{ count }}</button>
</template>
Możesz dowiedzieć się więcej o Przekształceniu reaktywności w dedykowanej sekcji. Należy pamiętać, że obecnie jest to wciąż eksperymentalne rozwiązanie i może ulec zmianie zanim zostanie sfinalizowane.