Základy reaktivity
API preference
Tato stránka a mnoho dalších kapitol dále v průvodci obsahuje různý obsah pro Options API a Composition API. Vaše aktuální preference je Composition API. Mezi API styly můžete přepínat pomocí přepínače "API preference" vlevo nahoře.
Deklarace reaktivního stavu
ref()
V rámci Composition API je doporučený způsob deklarace reaktivního stavu použitím funkce ref()
:
js
import { ref } from 'vue'
const count = ref(0)
ref()
přijímá vstupní parametr a vrací jej obalený uvnitř ref objektu s vlastností .value
:
js
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
Viz také: Typování
ref()
Chcete-li použít reaktivní stav v šabloně komponenty, deklarujte a vraťte jej z funkce setup()
v komponentě:
js
import { ref } from 'vue'
export default {
// `setup` je speciální hook určený pro Composition API.
setup() {
const count = ref(0)
// zpřístupnit stav pro šablonu komponenty
return {
count
}
}
}
template
<div>{{ count }}</div>
Všimněte si, že jsme nepotřebovali přidat .value
při použití ref v šabloně. Pro větší pohodlí jsou refs v šabloně automaticky rozbaleny (unwrapped) - s několika omezeními.
Můžete také měnit ref přímo v event handlerech:
template
<button @click="count++">
{{ count }}
</button>
Pro komplexnější logiku můžeme deklarovat funkce, které mění refs ve stejném scope, a vystavit je jako metody napříč stavem:
js
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
function increment() {
// v JavaScriptu je .value nutné
count.value++
}
// nezapomeňte funkce vystavit
return {
count,
increment
}
}
}
Vystavené funkce se obvykle používají jako event listenery:
template
<button @click="increment">
{{ count }}
</button>
Zde je funkční ukázka na Codepen, bez použití build nástrojů.
<script setup>
Ruční vystavování stavu a funkcí pomocí setup()
může být zbytečně složité. Naštěstí je to nutné pouze tehdy, když nepoužíváte build fázi. Při použití Single-File komponent (SFC) můžeme použití výrazně zjednodušit 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 nejvyšší úrovně a proměnné deklarované v <script setup>
jsou v šabloně stejné komponenty použitelné automaticky. Přemýšlejte o šablonách jako o JavaScript funkcích deklarovaných ve stejném scope - přirozeně mají přístup ke všemu, co je kolem ní deklarováno.
TIP
Ve zbytku průvodce budeme pro příklady kódu Composition API primárně používat syntaxi SFC + <script setup>
, protože to je pro Vue vývojáře nejběžnější použití.
Pokud SFC nepoužívate, pořád lze Composition API použít společně s možností setup()
.
Proč používat refs?
Možná se ptáte, proč potřebujeme refs s .value
místo obyčejných proměnných. Abychom to vysvětlili, musíme stručně vysvětlit, jak funguje systém reaktivity Vue.
Když použijete ref v šabloně a později změníte jeho hodnotu, Vue automaticky zjistí změnu a aktualizuje DOM. To je možné díky systému reaktivity založenému na sledování závislostí. Když je komponenta poprvé vykreslena, Vue sleduje každý ref, který byl během vykreslování použit. Později, když je ref měněn, spustí překreslení komponent, které ho sledují.
V běžném JavaScriptu není způsob, jak detekovat čtení nebo změnu obyčejných proměnných. Nicméně, můžeme zachytit operace get a set vlastností objektu pomocí getter a setter metod.
Vlastnost .value
dává Vue možnost detekovat, kdy byl ref čten nebo měněn. Interně Vue provádí sledování ve svém getteru a spouští překreslování ve svém setteru. Konceptuálně si můžete představit ref jako objekt, který vypadá takto:
js
// pseudokód, ne skutečná implementace
const mujRef = {
_value: 0,
get value() {
track()
return this._value
},
set value(novaHodnota) {
this._value = novaHodnota
trigger()
}
}
Další hezkou vlastností refs je, že je na rozdíl od obyčejných proměnných můžete předávat do funkcí a stále mít přístup k nejnovější hodnotě a reaktivnímu spojení. To je zvláště užitečné při refaktorování složité logiky do znovupoužitelného kódu.
Reaktivní systém je podrobněji diskutován v sekci Reaktivita podrobně.
Vnořená reaktivita
Refs mohou obsahovat hodnotu jakéhokoli typu, včetně hlubok vnořených objektů, polí a vestavěných JavaScipt datových struktur jako je Map
.
Ref svou hodnotu udělá hluboce reaktivní (deep reactivity). To znamená, že můžete očekávat detekci změn, i když změníte vnořené objekty nebo pole:
js
import { ref } from 'vue'
const obj = ref({
nested: { count: 0 },
arr: ['foo', 'bar']
})
function mutateDeeply() {
// bude fungovat podle očekávání
obj.value.nested.count++
obj.value.arr.push('baz')
}
Jiné než primitivní hodnoty jsou převedeny na reaktivní proxy přes reactive()
, o čemž mluvíme níže.
Je také možné potlačit hlubokou reaktivitu pomocí mělkých (shallow) refs. U mělké reaktivity je reaktivně sledován pouze přístup na .value
. Shallow refs mohou být použity pro optimalizaci výkonu díky potlačení náhladů na sledování velkých objektů nebo v případech, kdy je vnitřní stav pravován externí knihovnou.
Další informace:
Časování aktualizace DOM
Když měníte reaktivní stav, DOM se automaticky aktualizuje. Je však třeba poznamenat, že aktualizace DOM se neprovádí synchronně. Místo toho je Vue ukládá do fronty a aplikuje je až v „dalším tiknutí“ (next tick), aby se zajistilo, že každá komponenta se aktualizuje pouze jednou, bez ohledu na to, kolik změn stavu jste provedli.
Abyste po změně stavu počkali na dokončení aktualizace DOM, můžete použít globální API nextTick():
js
import { nextTick } from 'vue'
async function increment() {
count.value++
await nextTick()
// teď už je DOM aktualizován
}
reactive()
Existuje další způsob deklarace reaktivního stavu pomocí API reactive()
. Na rozdíl od ref, který obaluje vnitřní hodnotu do speciálního objektu, reactive()
činí objekt sám o sobě reaktivním:
js
import { reactive } from 'vue'
const state = reactive({ count: 0 })
Viz také: Typování
reactive()
Použití v šabloně:
template
<button @click="state.count++">
{{ state.count }}
</button>
Reaktivní objekty jsou JavaScript Proxies a chovají se jako normální objekty. Rozdíl spočívá v tom, že Vue je schopno zachytit čtení a změnu všech vlastností reaktivního objektu pro sledování a spouštění systému reaktivity.
reactive()
konvertuje objekt hluboce: vnořené objekty jsou při přístupu k nim také obaleny pomocí reactive()
. Interně je také voláno při použití ref()
s objektem jako hodnotou ref. Podobně jako u „mělkých“ refs existuje také API shallowReactive()
pro potlačení hluboké reaktivity.
Reaktivní proxy vs. originál
Je důležité si uvědomit, že hodnota vrácená z reactive()
je Proxy originálního objektu, který se nerovná původnímu objektu:
js
const raw = {}
const proxy = reactive(raw)
// proxy se NEROVNÁ původnímu objektu.
console.log(proxy === raw) // false
Pouze proxy je reaktivní - změny původního objektu aktualizace nevyvolají. Proto je nejlepší praxí při práci se systémem reaktivity ve Vue používat výhradně proxy verze vašeho stavu.
Aby byl zajištěn konzistentní přístup k proxy, volání reactive()
na stejném objektu vždy vrací stejné proxy a volání reactive()
na existující proxy také vrátí to samé proxy:
js
// volání reactive() na stejném objektu vrací stejné proxy
console.log(reactive(raw) === proxy) // true
// volání reactive() na proxy vrací sebe sama
console.log(reactive(proxy) === proxy) // true
Toto pravidlo platí i pro vnořené objekty. Kvůli vnořené reaktivitě jsou objekty vnořené uvnitř reaktivního objektu také proxy:
js
const proxy = reactive({})
const raw = {}
proxy.nested = raw
console.log(proxy.nested === raw) // false
Limity reactive()
API funkce reactive()
má dvě omezení:
Funguje pouze pro objektové typy (objekty, pole a typy kolekcí, jako jsou
Map
aSet
). Nemůže obsahovat primitivní datové typy, jako jestring
,number
neboboolean
.Vzhledem k tomu, že sledování reaktivity ve Vue funguje přes přístup k vlastnostem, musíme vždy zachovat stejný odkaz (reference) na reaktivní objekt. To znamená, že nemůžeme reaktivní objekt snadno „nahradit“, protože spojení reaktivity (reactivity connection) s prvním odkazem je ztraceno:
jslet state = reactive({ count: 0 }) // výše uvedená reference ({ count: 0 }) už není sledována (spojení reaktivity je ztraceno!) state = reactive({ count: 1 })
Není destructure-friendly: když destrukturujeme vlastnosti reaktivního objektu do lokálních proměnných nebo když tuto vlastnost předáme jako parametr funkci, ztratíme spojení reaktivity:
jsconst state = reactive({ count: 0 }) // count je při destrukturování odpojena od state.count let { count } = state // neovlivní původní stav count++ // funkce obdrží pouze prosté číslo // a nebude moci sledovat změny state.count // pro zachování reaktivity musíme předat celá objekt callSomeFunction(state.count)
Kvůli těmto omezením doporučujeme jako primární API pro deklaraci reaktivního stavu používat ref()
.
Bližší informace o rozbalování refs
Jako vlastnost reaktivního objektu
Ref je automaticky rozbalen, když je přistoupen nebo měněn jako vlastnost reaktivního objektu. Jinými slovy, chová se jako normální vlastnost:
js
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1
Pokud je ke vlastnosti propojené s existující instancí ref přiřazena nová, nahradí původní instanci:
js
const otherCount = ref(2)
state.count = otherCount
console.log(state.count) // 2
// původní instance ref je nyní od state.count odpojena
console.log(count.value) // 1
K rozbalení ref dochází pouze při vnoření do hluboce reaktivního objektu. Neplatí to, když se k němu přistupuje jako k vlastnosti mělkého reaktivního objektu.
Omezení při rozbalování refs v polích a kolekcích
Na rozdíl od reaktivních objektů nedochází k žádnému rozbalení, když se k ref přistupuje jako k prvku reaktivního pole nebo nativního typu kolekce, jako je Map
:
js
const books = reactive([ref('Vue 3 Guide')])
// zde je třeba .value
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// zde je třeba .value
console.log(map.get('count').value)
Omezení při rozbalování refs v šablonách
Rozbalování ref v šablonách platí pouze v případě, kdy je ref vlastností na nejvyšší úrovni v kontextu vykreslování šablony.
V následujícím příkladu jsou count
a object
vlastnostmi na nejvyšší úrovni, ale object.id
není:
js
const count = ref(0)
const object = { id: ref(1) }
Proto tento výraz funguje správně:
template
{{ count + 1 }}
...zatímco tento výraz NE:
template
{{ object.id + 1 }}
Výsledkem bude [object Object]1
, protože object.id
není při vyhodnocování výrazu rozbaleno a zůstává ref objektem. Pro opravu tohoto problému můžeme id
destrukturovat do vlastnosti na nejvyšší úrovni:
js
const { id } = object
template
{{ id + 1 }}
Nyní bude výsledek vykreslen jako 2
.
Další věc, na kterou je třeba si dát pozor, je, že ref se rozbalí, pokud je konečnou vyhodnocenou hodnotou textové interpolace (tj. tag {{ }}
), takže následující kód vykreslí 1
:
template
{{ object.id }}
Tato vymoženost textové interpolace pro větší pohodlí při psaní je ekvivalentní s {{ object.id.value }}
.