Správa stavu
Co je správa stavu?
Technicky vzato každá instance Vue komponenty již svůj vlastní reaktivní stav „spravuje“. Jako příklad si vezměme jednoduchou komponentu počítadla:
vue
<script setup>
import { ref } from 'vue'
// stav
const count = ref(0)
// akce
function increment() {
count.value++
}
</script>
<!-- zobrazení -->
<template>{{ count }}</template>
Je to samostatná jednotka s následujícími částmi:
- Stav - zdroj pravdy, který naši aplikaci řídí;
- Zobrazení - deklarativní mapování stavu;
- Akce - možné způsoby, jak se stav může změnit v reakci na uživatelské vstupy ze zobrazení.
Toto je jednoduché zobrazení konceptu „jednosměrného toku dat“:
Bohužel, jednoduchost začíná selhávat, když máme více komponent, které sdílejí společný stav:
- Více zobrazení může záviset na stejné části stavu.
- Akce z různých zobrazení mohou potřebovat měnit stejnou část stavu.
Pro případ jedna je možným řešením „“vyzdvihnutí“ sdíleného stavu na společného předka komponenty a poté ho předat dolů jako vlastnost (props). Nicméně ve stromech komponent s hlubokou hierarchií to rychle začíná být zdlouhavé a vede to k dalšímu problému známému jako drilling vlastností.
Pro případ dva se často uchylujeme k řešením, jako je přístup k přímým instancím komponent rodiče / potomka pomocí template refs nebo pokusy o změnu a synchronizaci více kopií stavu pomocí emitovaných událostí (emits). Obě tyto vzorce jsou křehké a rychle vedou k neudržovatelnému kódu.
Jednodušším a přímočařejším řešením je vyjmout sdílený stav z komponent a spravovat ho v globálním singleton objektu. Díky tomu se náš strom komponent stává velkým „pohledem“ (view) a jakákoli komponenta může přistupovat ke stavu nebo spouštět akce, bez ohledu na to, kde ve stromu se nacházejí!
Jednoduchá správa stavu s Reactivity API
Pokud máte část stavu, která by měla být sdílena mezi více instancemi, můžete k vytvoření reaktivního objektu použít reactive()
, a poté jej importovat do více komponent:
js
// store.js
import { reactive } from 'vue'
export const store = reactive({
count: 0
})
vue
<!-- ComponentA.vue -->
<script setup>
import { store } from './store.js'
</script>
<template>Od A: {{ store.count }}</template>
vue
<!-- ComponentB.vue -->
<script setup>
import { store } from './store.js'
</script>
<template>Od B: {{ store.count }}</template>
Kdykoli je nyní objekt store
změněn, obě komponenty <ComponentA>
a <ComponentB>
automaticky aktualizují svá zobrazení - máme jediný zdroj pravdy.
Ovšem to také znamená, že jakákoli komponenta, která store
importuje, ho může libovolně měnit:
template
<template>
<button @click="store.count++">
Od B: {{ store.count }}
</button>
</template>
Přestože to funguje v jednoduchých případech, globální stav, který může být libovolně měněn kteroukoli komponentou, nebude dlouhodobě udržitelný. Aby byla logika změny stavu centralizována stejně jako samotný stav, doporučuje se definovat v úložišti stavu metody s názvy, které vyjadřují úmysl akcí:
js
// store.js
import { reactive } from 'vue'
export const store = reactive({
count: 0,
increment() {
this.count++
}
})
template
<template>
<button @click="store.increment()">
Od B: {{ store.count }}
</button>
</template>
TIP
Všimněte si, že click handler používá store.increment()
s kulatými závorkami - je to nutné pro volání metody s odpovídajícím kontextem this
, protože to není metoda komponenty.
I když zde jako úložiště používáme jediný reaktivní objekt, můžete také sdílet reaktivní stav vytvořený pomocí jiných funkcí Reactivity API jako ref()
nebo computed()
, nebo dokonce vrátit globální stav z composable:
js
import { ref } from 'vue'
// globální stav, vytvořený v rámci modulu
const globalCount = ref(1)
export function useCount() {
// lokální stav, vytvořený pro každou komponentu
const localCount = ref(1)
return {
globalCount,
localCount
}
}
Skutečnost, že je reaktivní systém Vue od modelu komponent oddělený, ho činí extrémně flexibilním.
Úvahy o SSR
Pokud vytváříte aplikaci, která využívá vykreslování na serveru (SSR), výše uvedený vzor může vést k problémům kvůli sdílenému úložišti, které je singletonem sdíleným v rámci více požadavků. Tento problém je podrobněji popsán v průvodci SSR.
Pinia
I když naše vlastní řešení pro správu stavu v jednoduchých scénářích postačuje, existuje mnoho dalších věcí, které je třeba při vývoji rozsáhlých produkčních aplikací zvážit:
- Silnější konvence pro týmovou spolupráci
- Integrace s Vue DevTools, včetně časové osy, inspekce komponent a ladění s možností cestování časem
- Podpora Hot Module Replacement (HMR)
- Podpora Server-Side Rendering (SSR)
Pinia je knihovna pro správu stavu, která implementuje všechny výše uvedené funkce. Je udržována Vue týmem a funguje jak s Vue 2, tak s Vue 3.
Existující uživatelé mohou mít zkušenosti s Vuex, předchozí oficiální knihovnou pro správu stavu ve Vue. S Pinia, která plní v ekosystému stejnou roli, je nyní Vuex pouze v režimu údržby. Stále funguje, ale již nezískává nové funkce. Pro nové aplikace se doporučuje používat Pinia.
Pinia začala jako průzkum toho, jak by mohla další iterace Vuex vypadat, a zahrnuje mnoho nápadů z diskusí Vue týmu pro Vuex 5. Nakonec jsme si uvědomili, že Pinia již implementuje většinu toho, co jsme chtěli ve Vuex 5, a rozhodli jsme se ji doporučit jako nové řešení.
Ve srovnání s Vuex poskytuje Pinia jednodušší API s méně obřadným zápisem, nabízí API ve stylu Composition API a především má solidní podporu pro odvozování typů při použití s TypeScriptem.