Nuxt Tutorial 11 - State management
Druhou desítku článků o frameworku Nuxt začneme dílem o správě stavu naší aplikace a jak nám může pomoct Vue.js knihovna Pinia.
Kdy stav (ne)spravovat?
Kdy ne
První důležitý poznatek, který byste si z dnešního povídání měli odnést je, že v (primárně) frontendových aplikacích velmi často žádný stav udržovat vůbec nemusíte! Nedělejte stejnou chybu jako já a nevytvářejte zbytečně složité data udržující struktury tam, kde to není potřeba.
Často vám bude stačit, že si dynamická data potřebná pro zobrazení stránky stáhnete při jejím načítání. A při navigaci na jinou stránku stáhnete znovu jiná. A při dalším návratu zpět si znovu vezmete aktuální stav ze serveru. Sice to člověka svádí nedělat, protože to je přeci síťový provoz navíc, ale když jde o menší objemy dat, bude to často efektivnější než se mořit s efektivním cachováním. Ostatně je to původní princip internetu – prohlížeč požádá server o stránku s daty a dostane ji. Převracení této logiky a vytváření tlustých feature-rich klientských aplikací nemusí být vždy to pravé.
Toto samozřejmě nelze brát jako univerzální pravidlo. Pokud máte službu pro miliony uživatelů, pak možná opravdu nechcete, aby do vašeho serveru každou vteřinu bušilo milion lidí a neustále znovu dostávali všechny informace o svém uživatelském účtu, které si v příštích mnoha týdnech nehodlají měnit. Vždy zkrátka záleží na konkrétním případě. Ale pamatujte vždy na základní pravidlo optimalizace: Dokud to není potřeba, NEOPTIMALIZUJTE.
Kdy ano
Řekněme ale, že potřeba pamatovat si stejné hodnoty na více než jednom místě nastala. Například pracujete s opravdu složitým formulářem, který jste zcela logicky a správně rozdělili do většího množství do sebe zanořených komponent. To byl například use-case v aplikaci na správu žádostí o důchody, kde jsem poprvé použil Nuxt ve skutečně velkém projektu.
Pokud si v takovém případě zkusíte vystačit se základními prostředky Vue, brzy se při předávání vlastností setkáte s jevem známým jako prop drilling - v komponentách vyšší úrovně deklarujete proměnné pro vlastnosti jenom proto, abyste je mohli předat dále do potomků. Částečně si ještě můžete pomoct s technikou provide/inject
a práci s emitováním událostí v opačném směru nahradit správnou kombinací ref
a watch
. Patrně si však spíše začnete říkat, jestli už není ta pravá chvíle, kdy dává smysl sdílený stav napřed více částmi aplikace začít nějak organizovaně udržovat.
useState
Pro jednoduché případy tohoto typu má Nuxt už přímo v sobě zabudovanou pomocnou composable useState()
.
Do useState
předáte jako první parametr název a jako druhý nepovinnou funkci, která vrátí výchozí hodnotu, pokud stav není dosud naplněn. Poté je možné kdekoliv jinde v aplikaci useState
se stejným názvem zavolat a dostáváte odkaz na sdílená reaktivní data, které lze odevšad číst a upravovat.
Pozor - když jsem napsal „kdekoliv“, měl jsem na mysli kteroukoliv komponentu, ale definice by se měla vždy odehrávat uvnitř <script setup>
, jinak hrozí problémy s memory leaky. Plánujete-li jiné použití, poraďte se s dokumentací. Dalším omezením použití je fakt, že hodnoty uvnitř musí být serializovatelné, protože se převádí na JSON objekt.
Pokud máte pocit, že si s useState
nevystačíte, nebo chcete znát další alternativu, mám dobrou zprávu. V tomto článku ve skutečnosti celou dobu směřujeme ke svatému grálu state managementu ve Vue jménem Pinia.
Pinia
Pinia je knihovna pro jednoduchou, a přitom efektivní správu sdíleného stavu aplikace, přičemž už je dnes de-facto standardem ve Vue.js světě. Je možné, že někde ještě narazíte na Vuex, to už je však pouze legacy záležitost a nemá smysl na ni plýtvat pozorností. Raději si pojďme něco říct o nástroji s logem usměvavého ananasu.
Základním stavebním kamenem je „store“ - speciální composable-like objekt vytvořený pomocí funkce defineStore
, v jehož rámci vytvoříte definice reaktivních dat. Pinia k těmto datům následně umožňuje odkudkoliv z aplikace přistoupit, číst je i měnit. Změny se automaticky ihned všude projeví (za předpokladu, že nevhodnou implementací reaktivitu sami neztratíte). Přistupovat lze přímo na samotné objekty, není třeba explicitně definovat žádné gettery ani settery, ani se to nedoporučuje. Můžete však definovat složitější pomocné funkce – buďto de-facto computed
výpočty závislé na více prvcích nebo metody pro aktualizaci dat s vedlejšími efekty (například odeslání změn na backend). To vše je elegantně zabaleno jako composable s názvem useXYStore
(za předpokladu, že dodržíte běžnou konvenci).
Stejně jako je ve Vue možné programovat v Composable a Options API stylu, také Pinia stores je možné definovat oběma způsoby. Doporučím na začátek investovat chvilku mentální kapacity a naučit se rovnou psát Composition definice. Já sám to zpočátku neudělal, a i když se s tím dá žít, má to potenciál zbytečného zmatení. Pište své Pinia stores stejně jako své komponenty, ušetříte starosti sobě i svému okolí.
Tak jako integrovaný useState
, i Pinia si hravě poradí se Server Side Renderingem a zajistí soulad obsahu vykresleného ještě na serveru s výstupem hydratovaným v prohlížeči uživatele. Pinia žije s Vue reaktivitou v úplné symbióze a umožní ji využívat zcela přirozeně a naplno. Asi nepřekvapí perfektní podpora TypeScriptu a tedy možnost datový obsah podle potřeby typovat a využít typovou kontrolu během vývoje. Zároveň se plně integruje do nástrojů pro vývojáře, jako jsou Vue a Nuxt Devtools - z nich dokonce můžete modul jedním klikem myši v prohlížeči nainstalovat do nového projektu.
Osvojit si základy práce s knihovnou je velmi jednoduché. Pinia přitom umí daleko víc. Sám se ještě chystám absolvovat kurz Mastering Pinia od autora knihovny Eduarda San Martin Moroteho, abych se o možnostech a nejsprávnějším použití dovzdělal. Není to zadarmo, ale patrně se to vyplatí, pokud to s programováním ve Vue.js/Nuxtu začnete myslet vážně.
Do Nuxtu se Pinia podobně jako jiné nástroje integruje jednoduše pomocí dedikovaného modulu @pinia/nuxt
. Stačí tedy doplnit závislost v package.json
a přidat modul do seznamu v nuxt.config.ts
a můžeme začít spravovat stav aplikace.
Případová studie
Na tomto webu Master Coda jeden Pinia store mám - useFunStore
udržuje seznam vtipných obrázků, které si můžete prohlédnout. Trochu jsem si ulehčil práci a nepoužil čtení obsahových dat z databáze. Lépe řečeno mou databází je pouze soubor definující pole s metadaty. Jelikož se seznam obrázků příliš dynamicky nemění, je to dostatečně dobré a přitom jednoduché řešení. useFunStore
si při prvním použití natáhne definice obrázků z /data/fun.ts
, nastaví interní ukazatel na poslední obrázek a zpřístupňuje metody pro posun ukazatele vpřed a vzad. Pomocí toho se na stránce s obrázky naviguje klikáním na tlačítka.
Tento příklad je nyní nedílnou součástí toho, jak tento web funguje. Zároveň je dost možná v rozporu s tím, co jsem psal v úvodu, protože řeší udržování něčeho, co by udržováno být nemuselo. Například proto, že ne každý návštěvník prokliká obrázky všechny, a tedy není nutné je načítat dopředu, stačilo by ad-hoc během prohlížení. Jen mě to nenapadlo, když jsem stránku vymýšlel a zatím se mi to nechce předělávat. Aspoň ale vidíte, že k poznání, co dělat a co nedělat, vede klikatá cesta lemovaná pohrobky špatných rozhodnutí. Zároveň je pravda, že práce s knihovnou Pinia je natolik snadná a režijní náklady natolik malé, že si můžu dovolit tuto nedokonalost v kódu ponechat a zde se na ni z edukativních důvodů odkázat.
Druhá zajímavostí příkladu je, že je stále napsán v Options stylu. Ten mi totiž zpočátku přišel z nějakého důvodu srozumitelnější, ačkoliv je to v rozporu s Composition API přístupem, jenž jsem si osvojil všude jinde. I z tohoto pohledu bych už dnes tento kód napsal jinak. Ale zase to můžete brát jako důkaz, že i druhý přístup funguje, což je vždy to hlavní.
Na konkrétní implementaci se můžete podívat zde:
Demo projekt
Zdrojový kód ukázkové implementace ilustrující práci se stavem aplikace prostřednictvím knihovny Pinia v Nuxtu naleznete zde: nuxt-pinia @ GitHub
Projekt ukazuje, že k plné integraci Pinia v Nuxtu stačí aktivovat příslušný modul. Obsahuje definici Pinia stores v obou API stylech. Každý z nich je pak použit ve vlastní komponentně. K dispozici je metoda pro naplnění daty, která dokazuje, že jde o reaktivní stav – nové hodnoty se ihned projeví na obrazovce, resp. po kliknutí na tlačítko.
Shrnutí
Se správou stavu by se to ve frontendových aplikacích nemělo zbytečně přehánět. Pokud na ni ale dojde, má Nuxt skvělé nástroje, jak si se sdíleným stavem elegantně poradit. Pro jednodušší případy vlastní useState
, pro pokročilejší scénáře lze velice snadno integrovat dedikovanou Vue knihovnu Pinia.
V tomto článku jsem se zmínil, že na Master Coda není použita žádná databáze. Není to však v žádném případě proto, že by databáze byly pro Nuxt tabu! A aby nezůstalo jen u slov a slibů, další díl si na Nuxt a databáze posvítí.
- 15.02.2025▶ Nuxt Tutorial 11 - State management
- 05.10.2024Nuxt Tutorial 10 - Nuxt Content - Nuxt + Nuxt Content = tvorba obsahu bez zbytečných komplikací
- 15.09.2024Nuxt Tutorial 9 - Formuláře - Nuxt + FormKit = snadná tvorba formulářů
- 26.08.2024Nuxt Tutorial 8 - Nuxt UI - Nuxt - práce s modulem Nuxt UI dedikovaným pro práci s uživatelským rozhraním
- 24.08.2024Nuxt Tutorial 7 - UI integrace - Nuxt - jak snadno integrovat UI prvky
- 28.07.2024Nuxt Tutorial 6 - Vue.js intermezzo - Nuxt - alespoň stručný pohled na základní principy Vue.js, nad kterým je Nuxt postaven
- 05.05.2024Nuxt Tutorial 5 - Middleware - Nuxt - jak pracuje middleware
- 01.05.2024Nuxt Tutorial 4 - Serverová část - Nuxt - jak funguje serverová část
- 17.04.2024Nuxt Tutorial 3 - Utils & Composables - Nuxt - jak fungují složky /utils a /composables
- 31.03.2024Nuxt Tutorial 2 - Components & Pages - Nuxt - jak fungují složky /components a /pages
- 26.03.2024Nuxt Tutorial 1 - První kroky - Jak málo dnes stačí na funkční web