Documentazione · src/react/state
Lo stato
Nove file. Uno store con chiavi indirizzabili, slice dichiarate come dato, e reducer che il pacchetto esporta ma non installa.
Una chiave, un indirizzo
Lo stato è piatto: una mappa da chiave a valore. La chiave però può essere scopata per nodo — `nodo-42:open` — e lo scoping è quello che permette a tre accordion nella stessa pagina di avere ognuno il suo `open` senza collidere, e a un binding di puntarne uno preciso.
Le tre forme sono: `global` (chiave piatta, condivisa da tutti), `self` (isolata sul nodo corrente), `#<id>` (un nodo preciso). Il valore scopato resta comunque piatto nella mappa, il che tiene lo store semplice e le chiavi confrontabili per stringa.
state/uiStore.ts— lo store: valori, dichiarazioni, commit, sottoscrizione per chiavestate/expr.ts— lo scoping delle chiavi e la valutazione delle espressioni di binding
Ci si sveglia solo per le proprie chiavi
Un nodo reattivo dichiara quali chiavi guarda e si sottoscrive a quelle. Un commit su una chiave che non guarda non lo risveglia; un nodo che guarda zero chiavi — quello che ha solo un’animazione — non si sottoscrive affatto e non si risveglia mai.
Il guadagno grosso è proprio l’ultimo caso, ed è quello più facile da perdere in una riscrittura: prima ogni nodo reattivo si sottoscriveva a tutto lo store, quindi qualunque commit in qualunque punto della pagina risvegliava ogni nodo.
Collegare le chiavi però non era una riga: `useSyncExternalStore` chiama lo snapshot durante il render e di nuovo dopo, per vedere se è cambiato. Un selettore che costruisce un oggetto nuovo a ogni chiamata non supera mai quel confronto e va in loop. La memoizzazione sta dentro l’hook, dove chi chiama non la può sbagliare.
state/useSliceControl.ts— l’hook che un componente interattivo usa per registrare e guidare la propria slice
La forma delle slice è un dato
Quale stato espone un tipo di blocco è dichiarato in una mappa: nome, tipo, da quale campo prende il valore iniziale, quale evento lo cambia. Prima quella risposta esisteva solo dentro il componente — il wrapper dell’accordion chiamava l’hook con `name: 'open'` e nessuno fuori da lì lo sapeva.
Averla come dato serve a due cose che prima non si potevano fare: l’admin può MOSTRARE quale stato un blocco espone, e lo store può conservare la dichiarazione accanto al valore. Un `undefined` non distingue «chiave sbagliata» da «chiave vuota»; una dichiarazione sì.
La personalizzazione dei valori di default non è qui: qui c’è la forma e l’interazione. La customizzazione arriverà con lo spostamento della definizione nello Studio.
config/slices/blockSlices.ts— la mappa blocco → slice esposte, con tipo e campo di provenienzastate/defaults.ts— l’evento con cui l’hook scrive la slice di un nodo
I reducer li mette il progetto
`UIStateProvider` riceve i reducer attivi e non ne ha di default: il motore è vuoto. Il pacchetto li esporta ma non li installa — li installa il file `app-state.ts` che `init` scaffolda nel progetto, dove si vedono, si estendono e si possono togliere.
La scelta ha un costo dichiarato: `Reducer`, `Wiring`, `Effect` e `Mutation` sono contratto pubblico, perché è contro quei tipi che il consumer scrive la propria logica di dominio. Cambiare la loro forma è un breaking change, e questo va tenuto presente prima di cambiarla.
Una mutazione porta sempre una `key`, anche quando porta un `path`. Il `path` è previsto per scrivere su una foglia — `sections.hero.title` — che oggi non è implementata: chi la produrrà dovrà comunque valorizzare `key` con la RADICE del percorso. Il campo è dichiarato adesso, prima della pubblicazione, proprio perché aggiungerlo dopo cambierebbe una forma che sta già in mano a chi ha installato.
state/UIStateProvider.tsx— il provider: reducer attivi, store, wiringstate/index.ts— la porta dell’area
Gli effetti di pagina
Alcune conseguenze hanno bisogno del router: navigare, cambiare la query string. Le loro implementazioni non possono uscire da un componente, perché `useRouter` è un hook — quindi vengono registrate sul kernel dentro un `useEffect`, a runtime.
Il che però significava che al momento della CONFIGURAZIONE non esistevano: il plugin non aveva niente da leggere e l’admin non poteva proporle in un elenco. Da qui la separazione fra i NOMI, dichiarati staticamente, e le implementazioni, registrate quando c’è un browser.
state/pageEffects.ts— i nomi degli effetti di pagina, dichiarati staticamentestate/UIMSPageActions.tsx— le implementazioni, registrate sul kernel a runtimestate/UIMSPageReactive.tsx— lo stato dichiarato a livello di PAGINA, non di blocco