Grundlagen der Reaktivität
API-Präferenz
Diese Seite und viele andere Kapitel im weiteren Verlauf des Handbuchs enthalten unterschiedliche Inhalte für die Options-API und die Composition-API. Ihre aktuelle Präferenz ist Composition API. Mit den Schaltern "API-Einstellungen" oben in der linken Seitenleiste können Sie zwischen den API-Stilen wechseln.
Reaktiven Zustand deklarieren
Wir können ein reaktives Objekt oder Array mit der reactive()
Funktion:
js
import { reactive } from 'vue'
const state = reactive({ count: 0 })
Reaktive Objekte sind JavaScript Proxies und verhalten sich genau wie normale Objekte. Der Unterschied ist, dass Vue in der Lage ist, den Eigenschaftszugriff und die Mutationen eines reaktiven Objekts zu verfolgen. Wenn Sie neugierig auf die Details sind, erklären wir, wie Vue's Reaktivitätssystem funktioniert in Reaktivität in der Tiefe - aber wir empfehlen, ihn zu lesen, nachdem Sie den Hauptleitfaden gelesen haben.
Siehe auch: Typisierung Reaktiv
Um reaktive Zustände in einer Komponentenvorlage zu verwenden, deklarieren Sie sie und geben sie von der Funktion setup()
der Komponente zurück:
js
import { reactive } from 'vue'
export default {
// `setup` is a special hook dedicated for composition API.
setup() {
const state = reactive({ count: 0 })
// expose the state to the template
return {
state
}
}
}
template
<div>{{ state.count }}</div>
In ähnlicher Weise können wir Funktionen deklarieren, die den reaktiven Zustand im selben Bereich verändern, und sie als Methode neben dem Zustand offenlegen:
js
import { reactive } from 'vue'
export default {
setup() {
const state = reactive({ count: 0 })
function increment() {
state.count++
}
// Vergessen Sie nicht, auch die Funktion freizulegen.
return {
state,
increment
}
}
}
Ausgesetzte Methoden werden in der Regel als Ereignis-Listener verwendet:
template
<button @click="increment">
{{ state.count }}
</button>
<script setup>
Die manuelle Freigabe von Zuständen und Methoden über setup()
kann sehr aufwendig sein. Glücklicherweise ist es nur notwendig, wenn kein Build-Schritt verwendet wird. Bei der Verwendung von Single-File Components (SFCs) können wir die Verwendung mit <script setup>
stark vereinfachen:
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>
Versuchen Sie es in Playground
Top-Level-Importe und Variablen, die in <script setup>
deklariert werden, sind automatisch in der Vorlage der gleichen Komponente verwendbar.
Für den Rest des Leitfadens werden wir hauptsächlich die SFC +
<script setup>
Syntax für Composition API Code Beispiele verwenden, da dies die häufigste Verwendung für Vue Entwickler ist.
DOM-Aktualisierungszeitpunkt
Wenn Sie den reaktiven Zustand ändern, wird das DOM automatisch aktualisiert. Es ist jedoch zu beachten, dass die DOM-Aktualisierungen nicht synchron durchgeführt werden. Stattdessen puffert Vue sie bis zum "nächsten Tick" im Aktualisierungszyklus, um sicherzustellen, dass jede Komponente nur einmal aktualisiert werden muss, egal wie viele Zustandsänderungen Sie vorgenommen haben.
Um zu warten, bis die DOM-Aktualisierung nach einer Zustandsänderung abgeschlossen ist, können Sie die globale API nextTick() verwenden:
js
import { nextTick } from 'vue'
function increment() {
state.count++
nextTick(() => {
// access updated DOM
})
}
Tiefe Reaktivität
In Vue ist der Zustand standardmäßig sehr reaktiv. Das bedeutet, dass Sie erwarten können, dass Änderungen erkannt werden, selbst wenn Sie verschachtelte Objekte oder Arrays verändern:
js
import { reactive } from 'vue'
const obj = reactive({
nested: { count: 0 },
arr: ['foo', 'bar']
})
function mutateDeeply() {
// werden diese wie erwartet funktionieren.
obj.nested.count++
obj.arr.push('baz')
}
Es ist auch möglich, explizit shallow reactive objects zu erstellen, bei denen die Reaktivität nur auf der Stammebene verfolgt wird, aber diese werden in der Regel nur in fortgeschrittenen Anwendungsfällen benötigt.
Reaktiver Proxy vs. Original
Es ist zu beachten, dass der von reactive()
zurückgegebene Wert ein Proxy des ursprünglichen Objekts ist, das nicht mit dem ursprünglichen Objekt identisch ist:
js
const raw = {}
const proxy = reactive(raw)
// Proxy ist NICHT mit dem Original identisch.
console.log(proxy === raw) // false
Nur der Proxy ist reaktiv - eine Änderung des Originalobjekts löst keine Aktualisierungen aus. Daher ist die beste Praxis bei der Arbeit mit dem Reaktivitätssystem von Vue, ausschließlich die Proxy-Versionen Ihres Zustands zu verwenden.
Um einen konsistenten Zugriff auf den Proxy zu gewährleisten, gibt der Aufruf von reactive()
auf dasselbe Objekt immer denselben Proxy zurück, und der Aufruf von reactive()
auf einen bestehenden Proxy gibt ebenfalls denselben Proxy zurück:
js
// der Aufruf von reactive() für dasselbe Objekt liefert denselben Proxy
console.log(reactive(raw) === proxy) // true
// der Aufruf von reactive() auf einem Proxy gibt sich selbst zurück
console.log(reactive(proxy) === proxy) // true
Diese Regel gilt auch für verschachtelte Objekte. Aufgrund der tiefen Reaktivität sind verschachtelte Objekte innerhalb eines reaktiven Objekts ebenfalls Proxys:
js
const proxy = reactive({})
const raw = {}
proxy.nested = raw
console.log(proxy.nested === raw) // false
Beschränkungen von reactive()
Die API reactive()
hat zwei Einschränkungen:
Es funktioniert nur für Objekttypen (Objekte, Arrays und Sammlungstypen wie
Map
undSet
). Es kann keine primitiven Typen wiestring
,number
oderboolean
enthalten.Da das Reaktivitäts-Tracking von Vue über den Property-Zugriff funktioniert, müssen wir immer denselben Verweis auf das reaktive Objekt behalten. Das bedeutet, dass wir ein reaktives Objekt nicht einfach "ersetzen" können, da die Reaktivitätsverbindung zur ersten Referenz verloren geht:
jslet state = reactive({ count: 0 }) // the above reference ({ count: 0 }) is no longer being tracked (reactivity connection is lost!) state = reactive({ count: 1 })
Das bedeutet auch, dass wir die Verbindung zur Reaktivität verlieren, wenn wir die Eigenschaft eines reaktiven Objekts in lokalen Variablen zuweisen oder destrukturieren oder wenn wir diese Eigenschaft an eine Funktion übergeben:
jsconst state = reactive({ count: 0 }) // n is a local variable that is disconnected // from state.count. let n = state.count // does not affect original state n++ // count is also disconnected from state.count. let { count } = state // does not affect original state count++ // the function receives a plain number and // won't be able to track changes to state.count callSomeFunction(state.count)
Reaktive Variablen mit ref()
Um die Einschränkungen von reactive()
zu adressieren, bietet Vue auch eine ref()
Funktion, die es uns erlaubt, reaktive "refs " zu erstellen, die jeden Werttyp enthalten können:
js
import { ref } from 'vue'
const count = ref(0)
ref()
nimmt das Argument und gibt es in einem ref-Objekt mit der Eigenschaft .value
zurück:
js
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
Siehe auch: Typing Refs
Ähnlich wie die Eigenschaften eines reaktiven Objekts ist auch die Eigenschaft .value
eines ref reaktiv. Darüber hinaus wandelt ref bei der Aufnahme von Objekttypen seinen .value
automatisch mit reactive()
um.
Eine Referenz, die einen Objektwert enthält, kann reaktiv das gesamte Objekt ersetzen:
js
const objectRef = ref({ count: 0 })
// this works reactively
objectRef.value = { count: 1 }
Refs können auch an Funktionen übergeben oder aus einfachen Objekten destrukturiert werden, ohne dass die Reaktivität verloren geht:
js
const obj = {
foo: ref(1),
bar: ref(2)
}
// the function receives a ref
// it needs to access the value via .value but it
// will retain the reactivity connection
callSomeFunction(obj.foo)
// still reactive
const { foo, bar } = obj
Mit anderen Worten, ref()
ermöglicht es uns, einen "Verweis" auf einen beliebigen Wert zu erstellen und diesen weiterzugeben, ohne die Reaktivität zu verlieren. Diese Fähigkeit ist sehr wichtig, da sie häufig bei der Extraktion von Logik in Composable Functions verwendet wird.
Ref Unwrapping in Templates
Wenn auf refs als Top-Level-Eigenschaften in der Vorlage zugegriffen wird, werden sie automatisch "ausgepackt", so dass es nicht notwendig ist, .value
zu verwenden. Hier ist das vorherige Zähler-Beispiel, das stattdessen ref()
verwendet:
vue
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">
{{ count }} <!-- no .value needed -->
</button>
</template>
Versuchen Sie es in Playground
Beachten Sie, dass das Unwrapping nur gilt, wenn die Referenz eine Eigenschaft der obersten Ebene im Renderkontext der Vorlage ist. Ein Beispiel: foo
ist eine Top-Level-Eigenschaft, aber object.foo
ist keine.
Nehmen wir also das folgende Objekt:
js
const object = { foo: ref(1) }
Der folgende Ausdruck wird NICHT wie erwartet funktionieren:
template
{{ object.foo + 1 }}
Das gerenderte Ergebnis wird [object Object]
sein, weil object.foo
ein ref object ist. Wir können das beheben, indem wir foo
zu einer Eigenschaft der obersten Ebene machen:
js
const { foo } = object
template
{{ foo + 1 }}
Das Rendering-Ergebnis wird nun 2
sein.
Zu beachten ist, dass ein ref auch dann ausgepackt wird, wenn es sich um den endgültigen Wert einer Textinterpolation handelt (d.h. ein {{ }}
-Tag), so dass der folgende Text als 1
dargestellt wird:
template
{{ object.foo }}
Dies ist nur eine praktische Funktion der Textinterpolation und entspricht {{ object.foo.value }}
.
Ref Unwrapping in reaktiven Objekten
Wenn auf ein ref
als Eigenschaft eines reaktiven Objekts zugegriffen wird oder es verändert wird, wird es auch automatisch entpackt, so dass es sich wie eine normale Eigenschaft verhält:
js
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1
Wenn eine neue Referenz einer Eigenschaft zugewiesen wird, die mit einer bestehenden Referenz verknüpft ist, ersetzt sie die alte Referenz:
js
const otherCount = ref(2)
state.count = otherCount
console.log(state.count) // 2
// original ref is now disconnected from state.count
console.log(count.value) // 1
Ref unwrapping findet nur statt, wenn es innerhalb eines tiefen reaktiven Objekts verschachtelt ist. Sie gilt nicht, wenn auf sie als Eigenschaft eines shallow reactive object zugegriffen wird.
Ref Unwrapping in Arrays und Collections
Im Gegensatz zu reaktiven Objekten wird kein Unwrapping durchgeführt, wenn auf den ref als Element eines reaktiven Arrays oder eines nativen Auflistungstyps wie Map
zugegriffen wird:
js
const books = reactive([ref('Vue 3 Guide')])
// need .value here
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// need .value here
console.log(map.get('count').value)
ReAktivität Transformieren
Die Verwendung von .value
mit refs ist ein Nachteil, der durch die sprachlichen Einschränkungen von JavaScript bedingt ist. Mit Compile-Time-Transformationen können wir jedoch die Ergonomie verbessern, indem wir automatisch .value
an den entsprechenden Stellen anhängen. Vue bietet eine Kompilierzeittransformation, die es uns erlaubt, das frühere "counter"-Beispiel so zu schreiben:
vue
<script setup>
let count = $ref(0)
function increment() {
// no need for .value
count++
}
</script>
<template>
<button @click="increment">{{ count }}</button>
</template>
Sie können mehr über Reactivity Transform in dem entsprechenden Abschnitt erfahren. Bitte beachten Sie, dass die Funktion derzeit noch experimentell ist und sich vor ihrer Fertigstellung noch ändern kann.