Transformace reaktivity
Zastaralá funkce
Transformace reaktivity (Reactivity transform) byla experimentální funkcí a byla odstraněna v release verzi 3.4. Přečtěte si zdůvodnění.
Pokud si ji stále přejete používat, je nyní dostupná prostřednictvím pluginu Vue Macros.
Specifické pro Composition API
Transformace reaktivity je funkcí specifickou pro Composition API a vyžaduje build fázi.
Refs vs. reaktivní proměnné
Od zavedení Composition API je jednou z hlavních nevyřešených otázek použití refs oproti reaktivním objektům. Při destrukturování reaktivních objektů je snadné ztratit reaktivitu, zatímco při použití refs je obtížné všude používat .value
. Navíc, pokud nepoužíváte typový systém, .value
snadno přehlédnete.
Vue Reactivity Transform je compile-time transformace, která nám umožňuje psát kód takto:
vue
<script setup>
let count = $ref(0)
console.log(count)
function increment() {
count++
}
</script>
<template>
<button @click="increment">{{ count }}</button>
</template>
Metoda $ref()
je compile-time makro: není to skutečná metoda, která by se spustila za běhu. Místo toho ji Vue překladač používá jako nápovědu, aby s výslednou proměnnou count
zacházel jako s reaktivní proměnnou.
Reaktivní proměnné lze přistupovat a přiřazovat stejně jako běžné proměnné, ale tyto operace jsou kompilovány do refs s .value
. Například část <script>
výše uvedené komponenty je kompilována do:
js
import { ref } from 'vue'
let count = ref(0)
console.log(count.value)
function increment() {
count.value++
}
Každé Reactivity API, které vrací refs, bude mít ekvivalentní makro s předponou $
. Tato API zahrnují:
ref
->$ref
computed
->$computed
shallowRef
->$shallowRef
customRef
->$customRef
toRef
->$toRef
Tato makra jsou globálně dostupná a nemusí být importována, pokud je transformace reaktivity povolena. Nicméně, můžete je volitelně importovat z vue/macros
, pokud chcete být explicitnější:
js
import { $ref } from 'vue/macros'
let count = $ref(0)
Destrukturování s $()
Je běžné, že composable funkce vrací ref objekt a k jejich získání se používá destrukturování. K tomuto účelu poskytuje transformace reaktivity makro $()
:
js
import { useMouse } from '@vueuse/core'
const { x, y } = $(useMouse())
console.log(x, y)
Přeložený výstup:
js
import { toRef } from 'vue'
import { useMouse } from '@vueuse/core'
const __temp = useMouse(),
x = toRef(__temp, 'x'),
y = toRef(__temp, 'y')
console.log(x.value, y.value)
Zapamatujte si, že pokud x
již je ref, toRef(__temp, 'x')
ho jednoduše vrátí tak, jak je, a žádný další ref nebude vytvořen. Pokud destrukturovaná hodnota není ref (např. funkce), stále to funguje - hodnota bude obalena v ref, aby zbytek kódu fungoval, jak se očekává.
Destrukturovaní s $()
funguje jak na reaktivních objektech, tak na obyčejných objektech obsahujících refs.
Převod existujících refs na reaktivní proměnné s $()
V některých případech můžeme mít obalující funkce, které také vrací refs. Nicméně, Vue překladač nebude schopen předem poznat, že funkce vrátí ref. V takových případech lze makro $()
použít i k převodu existujících refs na reaktivní proměnné:
js
function myCreateRef() {
return ref(0)
}
let count = $(myCreateRef())
Destrukturování reaktivních vlastností
S aktuálním použitím defineProps()
v <script setup>
existují dva problémy:
Podobně jako
.value
, musíte vždy přistupovat k vlastnostem (props) jako kprops.x
, abyste zachovali reaktivitu. To znamená, že nemůžete destrukturovatdefineProps
, protože výsledné destrukturované proměnné nejsou reaktivní a nebudou se aktualizovat.Při použití pouze typové deklarace props neexistuje snadný způsob, jak deklarovat výchozí hodnoty. Pro tento účel jsme zavedli API
withDefaults()
, ale není moc pohodlné ho používat.
Tyto problémy můžeme řešit pomocí compile-time transformace, když se defineProps
použije s destrukturováním, podobně jako jsme viděli dříve s $()
:
html
<script setup lang="ts">
interface Props {
msg: string
count?: number
foo?: string
}
const {
msg,
// výchozí hodnota funguje
count = 1,
// lokální přejmenování také funguje
// zde přejmenováváme `props.foo` na `bar`
foo: bar
} = defineProps<Props>()
watchEffect(() => {
// bude logovat vždy, když se props změní
console.log(msg, count, bar)
})
</script>
Výše uvedený kód bude zkompilován na následující runtime ekvivalent:
js
export default {
props: {
msg: { type: String, required: true },
count: { type: Number, default: 1 },
foo: String
},
setup(props) {
watchEffect(() => {
console.log(props.msg, props.count, props.foo)
})
}
}
Zachování reaktivity přes hranice funkcí
Zatímco reaktivní proměnné nás zbavují nutnosti používat všude .value
, vytváří problém „ztráty reaktivity“, když reaktivní proměnné předáváme přes hranice funkcí. To se může stát ve dvou případech:
Předání do funkce jako parametr
Předpokládejme funkci, která očekává jako parametr ref, např.:
ts
function trackChange(x: Ref<number>) {
watch(x, (x) => {
console.log('x změněno!')
})
}
let count = $ref(0)
trackChange(count) // nefunguje!
Výše uvedený případ nebude fungovat podle očekávání, protože se překládá na:
ts
let count = ref(0)
trackChange(count.value)
Zde je count.value
předáváno jako číslo, zatímco trackChange
očekává skutečný ref. To lze opravit obalením count
do $$()
před jeho předáním:
diff
let count = $ref(0)
- trackChange(count)
+ trackChange($$(count))
Výše uvedený kód se překládá na:
js
import { ref } from 'vue'
let count = ref(0)
trackChange(count)
Jak můžeme vidět, $$()
je makro, které slouží jako escape hint: reaktivní proměnné uvnitř $$()
nedostanou přidáno .value
.
Návrat v rámci rozsahu funkce
Reaktivita může být také ztracena, pokud jsou reaktivní proměnné použity přímo v návratovém výrazu:
ts
function useMouse() {
let x = $ref(0)
let y = $ref(0)
// naslouchat události mousemove...
// nefunguje!
return {
x,
y
}
}
Výše uvedený návratový výraz se překládá na:
ts
return {
x: x.value,
y: y.value
}
Abychom zachovali reaktivitu, měli bychom vracet skutečné refs, ne aktuální hodnotu v době návratu.
Opět můžeme k opravě použít $$()
. V tomto případě lze $$()
použít přímo na vráceném objektu - jakýkoli odkaz na reaktivní proměnné uvnitř volání $$()
si zachová odkaz na jejich podkladové refs:
ts
function useMouse() {
let x = $ref(0)
let y = $ref(0)
// naslouchat události mousemove...
// opraveno
return $$({
x,
y
})
}
Použití $$()
na destrukturovaných vlastnostech
$$()
funguje i na destrukturovaných vlastnostech (props), protože jsou to také reaktivní proměnné. Překladač je pro větší efektivitu převede pomocí toRef
:
ts
const { count } = defineProps<{ count: number }>()
passAsRef($$(count))
se překládá na:
js
setup(props) {
const __props_count = toRef(props, 'count')
passAsRef(__props_count)
}
Integrace TypeScriptu
Vue poskytuje typy pro tyto makra (dostupné globálně) a všechny typy budou fungovat správně. Nejsou zde žádné neslučitelnosti se standardními sémantikami TypeScriptu, takže syntaxe bude fungovat se všemi existujícími nástroji.
To také znamená, že makra mohou fungovat v jakémkoli souboru, kde jsou povoleny platné JS / TS - nejen uvnitř Vue SFC.
Protože makra jsou dostupná globálně, jejich typy musí být explicitně odkazovány (např. v souboru env.d.ts
):
ts
/// <reference types="vue/macros-global" />
Při explicitním importu z vue/macros
bude typ fungovat bez deklarování globálních proměnných.
Explicitní povolení
Varování
Následující se vztahuje pouze na verze Vue 3.3 a nižší. Její podpora byla odstraněna ve verze 3.4 Vue Core a z @vitejs/plugin-vue
verze 5.0+. Pokud plánujete transformaci nadále používat, přejděte prosím na Vue Macros.
Vite
- Vyžaduje
@vitejs/plugin-vue@>=2.0.0
- Používá se u SFC a js(x)/ts(x) souborů. Před aplikací transformace se na soubory provede rychlá kontrola použití, takže pro soubory, které makra nepoužívají, by neměly být žádné náklady na výkon.
- Poznámka:
reactivityTransform
je nyní volbou na nejvyšší úrovni pluginu místo vnořeného jakoscript.refSugar
, protože ovlivňuje nejen SFC.
js
// vite.config.js
export default {
plugins: [
vue({
reactivityTransform: true
})
]
}
vue-cli
- V současné době ovlivňuje pouze SFC
- Vyžaduje
vue-loader@>=17.0.0
js
// vue.config.js
module.exports = {
chainWebpack: (config) => {
config.module
.rule('vue')
.use('vue-loader')
.tap((options) => {
return {
...options,
reactivityTransform: true
}
})
}
}
Čistý webpack
+ vue-loader
- V současné době ovlivňuje pouze SFC
- Vyžaduje
vue-loader@>=17.0.0
js
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
reactivityTransform: true
}
}
]
}
}