import { create } from 'zustand';
interface UserState {
name: string;
role: string;
updateName: (name: string) => void;
}
// Store-ul Zustand funcționează complet în afara contextului React
export const useUserStore = create<UserState>((set) => ({
name: 'Eduard',
role: 'Senior Dev',
updateName: (name) => set({ name }),
}));
// În componentă, selectăm DOAR proprietatea de care avem nevoie
// Componenta se va re-randa doar dacă se schimbă 'name'
export function ProfileHeader() {
const name = useUserStore((state) => state.name);
return <h1>Salut, {name}!</h1>;
}Ne batem capul prea mult cu state management-ul în React și adesea sărim direct la extreme. Am văzut echipe care bagă Redux Toolkit pentru un simplu switch de temă, dar și oameni care se chinuie cu 15 Context-uri suprapuse până când crapă performanța aplicației. Hai să vedem unde tragem linia între useReducer și Zustand, pe cifre și cazuri reale din producție.
Cazul de utilizare pentru useReducer: Statul local și complex
Am avut acum doi ani un proiect cu un configurator industrial. Aveam în jur de 40 de inputuri interdependente: dacă utilizatorul alegea o anumită pompă, se dezactivau trei valve și se schimba automat tot calculul de presiune pe backend.
Pentru așa ceva, useReducer e sfânt. Toată logica de business stă într-un singur loc, curat, în afara componentei React. Nu ai nevoie de nicio librărie externă, nu adaugi kilobaiți în bundle-ul final și totul e ușor de testat unitar.
Trade-off-ul apare când starea asta devine globală. În momentul în care vrei să partajezi datele cu un header aflat la trei niveluri distanță în arbore, începe durerea. Dacă pui reducerul într-un Context nativ, ai dat de dracu'. La fiecare dispatch se vor re-randa toate componentele consumatoare, chiar dacă ele folosesc doar o singură bifă din tot graful ăla de stare. Am pățit asta pe un dashboard cu peste 8k de useri activi, iar frame rate-ul scăzuse sub 30 FPS pe ecranele mai slabe din cauza re-randărilor inutile.
De ce Zustand este, de fapt, ce trebuia să fie Context-ul
Când starea devine globală și ai nevoie de performanță fără boilerplate, Zustand e regele nedeclarat. Spre deosebire de Context API, Zustand folosește un model de publish-subscribe sub capotă. Asta înseamnă că re-randările sunt extrem de targetate.
Dacă ai nevoie doar de numele utilizatorului într-un colț de ecran, asiguri un selector simplu. Doar acea componentă se va re-randa atunci când numele se schimbă, restul aplicației rămânând neatinsă.
La un proiect de e-commerce la care am lucrat recent, mutarea coșului de cumpărături dintr-un Context cu useReducer în Zustand ne-a adus o îmbunătățire de aproape 30% la timpul de interacțiune pe paginile de produs. În plus, am scăpat de acel nesting oribil de provideri în App.tsx.
Trade-off-urile de care nu vorbește nimeni
Să fim sinceri. Zustand e genial pentru că e extrem de permisiv, dar asta e și cea mai mare problemă a lui. Dacă lucrezi într-o echipă mare de developeri, fără niște reguli stricte și documentate, store-ul de Zustand devine repede o groapă de gunoi. Fiecare coleg va scrie acțiunile cum îl taie capul, direct în componente sau împrăștiate prin fișiere.
Pe de altă parte, useReducer te forțează să folosești acel pattern rigid de dispatch({ type: 'ACTION_NAME' }). E mai greu de scris la început, dar e mult mai ușor de urmărit și debugat într-un flow complex, mai ales dacă folosești TypeScript pentru a tipiza acțiunile.
Regula mea de aur e simplă: dacă starea aparține unei singure pagini sau unui singur widget complex, folosesc useReducer local. Dacă starea trebuie accesată din mai multe rute sau afectează performanța prin re-randări masive, Zustand e soluția fără discuții.
Voi cum procedați în proiectele voastre? Mai folosește cineva Context nativ pentru stări dinamice sau ați trecut toți pe Zustand?