Skip to content

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>
vue
<script>
export default {
  // stav
  data() {
    return {
      count: 0
    }
  },
  // akce
  methods: {
    increment() {
      this.count++
    }
  }
}
</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“:

Diagram toku dat

Bohužel, jednoduchost začíná selhávat, když máme více komponent, které sdílejí společný stav:

  1. Více zobrazení může záviset na stejné části stavu.
  2. 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

V Options API se reaktivní data deklarují pomocí možnosti data(). Interně je objekt vrácený z data() učiněn reaktivním pomocí funkce reactive(), která je také dostupná jako veřejné 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>
vue
<!-- ComponentA.vue -->
<script>
import { store } from './store.js'

export default {
  data() {
    return {
      store
    }
  }
}
</script>

<template>Od A: {{ store.count }}</template>
vue
<!-- ComponentB.vue -->
<script>
import { store } from './store.js'

export default {
  data() {
    return {
      store
    }
  }
}
</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.

Správa stavu has loaded