
Eine kleine, schnelle und skalierbare Bearbones-Staatsmanagementlösung unter Verwendung vereinfter Flussprinzipien. Hat eine bequeme API, die auf Hooks basiert, ist nicht kesselplier oder machte auf.
Ignorieren Sie es nicht, weil es süß ist. Es hat ziemlich die Krallen, viel Zeit wurde mit gemeinsamen Fallstricken aufgewendet, wie das gefürchtete Zombie -Kindsproblem, die Reaktion der Parallelität und den Kontextverlust zwischen gemischten Renderern. Es kann der einzige Staatsmanager im React-Raum sein, der all diese Rechten einbringt.
Sie können hier eine Live -Demo ausprobieren.
npm install zustand Ihr Geschäft ist ein Haken! Sie können alles hineinlegen: Primitive, Objekte, Funktionen. Der Staat muss immutiert aktualisiert werden und die set -Funktion verschmilzt den Status, um ihm zu helfen.
import { create } from 'zustand'
const useBearStore = create ( ( set ) => ( {
bears : 0 ,
increasePopulation : ( ) => set ( ( state ) => ( { bears : state . bears + 1 } ) ) ,
removeAllBears : ( ) => set ( { bears : 0 } ) ,
} ) ) Verwenden Sie den Haken überall, es werden keine Anbieter benötigt. Wählen Sie Ihren Status aus und die Komponente wird bei Änderungen erneut übertragen.
function BearCounter ( ) {
const bears = useBearStore ( ( state ) => state . bears )
return < h1 > { bears } around here ... </ h1 >
}
function Controls ( ) {
const increasePopulation = useBearStore ( ( state ) => state . increasePopulation )
return < button onClick = { increasePopulation } > one up </ button >
}Sie können, aber bedenken Sie, dass die Komponente jede staatliche Änderung aktualisiert!
const state = useBearStore ( ) Es erkennt Änderungen mit strikter Gleichheit (alt === neu) standardmäßig. Dies ist effizient für Atomstatus-Picks.
const nuts = useBearStore ( ( state ) => state . nuts )
const honey = useBearStore ( ( state ) => state . honey )Wenn Sie ein einzelnes Objekt mit mehreren State-Picks im Inneren konstruieren möchten, ähnlich wie bei den MapStatetoprops von Redux, können Sie Usshallow verwenden, um unnötige Wiedererlebende zu verhindern, wenn sich die Ausgabe des Selektors nicht entsprechend dem flachen Gleichgewicht ändert.
import { create } from 'zustand'
import { useShallow } from 'zustand/react/shallow'
const useBearStore = create ( ( set ) => ( {
nuts : 0 ,
honey : 0 ,
treats : { } ,
// ...
} ) )
// Object pick, re-renders the component when either state.nuts or state.honey change
const { nuts , honey } = useBearStore (
useShallow ( ( state ) => ( { nuts : state . nuts , honey : state . honey } ) ) ,
)
// Array pick, re-renders the component when either state.nuts or state.honey change
const [ nuts , honey ] = useBearStore (
useShallow ( ( state ) => [ state . nuts , state . honey ] ) ,
)
// Mapped picks, re-renders the component when state.treats changes in order, count or keys
const treats = useBearStore ( useShallow ( ( state ) => Object . keys ( state . treats ) ) ) Für mehr Kontrolle über die Wiederherstellung können Sie jede benutzerdefinierte Gleichstellungsfunktion bereitstellen (dieses Beispiel erfordert die Verwendung von createWithEqualityFn ).
const treats = useBearStore (
( state ) => state . treats ,
( oldTreats , newTreats ) => compare ( oldTreats , newTreats ) ,
) Die set -Funktion hat ein zweites Argument, das standardmäßig false . Anstatt zu verschmelzen, ersetzt es das Statusmodell. Achten Sie darauf, Teile, auf die Sie sich verlassen, nicht wie Aktionen auszulöschen.
import omit from 'lodash-es/omit'
const useFishStore = create ( ( set ) => ( {
salmon : 1 ,
tuna : 2 ,
deleteEverything : ( ) => set ( { } , true ) , // clears the entire store, actions included
deleteTuna : ( ) => set ( ( state ) => omit ( state , [ 'tuna' ] ) , true ) ,
} ) ) Rufen Sie einfach set wenn Sie bereit sind, Zustand ist es egal, ob Ihre Aktionen asynchronisiert sind oder nicht.
const useFishStore = create ( ( set ) => ( {
fishies : { } ,
fetch : async ( pond ) => {
const response = await fetch ( pond )
set ( { fishies : await response . json ( ) } )
} ,
} ) ) set ermöglicht set(state => result) , aber Sie haben immer noch Zugriff auf Status außerhalb davon durch get .
const useSoundStore = create ( ( set , get ) => ( {
sound : 'grunt' ,
action : ( ) => {
const sound = get ( ) . sound
. . . Manchmal müssen Sie auf nicht reaktive Weise auf den Zustand zugreifen oder auf den Laden reagieren. In diesen Fällen verfügt der resultierende Haken mit Nutzfunktionen an seinen Prototyp.
const useDogStore = create ( ( ) => ( { paw : true , snout : true , fur : true } ) )
// Getting non-reactive fresh state
const paw = useDogStore . getState ( ) . paw
// Listening to all changes, fires synchronously on every change
const unsub1 = useDogStore . subscribe ( console . log )
// Updating state, will trigger listeners
useDogStore . setState ( { paw : false } )
// Unsubscribe listeners
unsub1 ( )
// You can of course use the hook as you always would
function Component ( ) {
const paw = useDogStore ( ( state ) => state . paw )
. . . Wenn Sie sich mit einem Selektor abonnieren müssen, hilft subscribeWithSelector Middleware.
Mit diesem Middleware -Abonnement akzeptiert subscribe eine zusätzliche Signatur:
subscribe ( selector , callback , options ?: { equalityFn , fireImmediately } ) : Unsubscribe import { subscribeWithSelector } from 'zustand/middleware'
const useDogStore = create (
subscribeWithSelector ( ( ) => ( { paw : true , snout : true , fur : true } ) ) ,
)
// Listening to selected changes, in this case when "paw" changes
const unsub2 = useDogStore . subscribe ( ( state ) => state . paw , console . log )
// Subscribe also exposes the previous value
const unsub3 = useDogStore . subscribe (
( state ) => state . paw ,
( paw , previousPaw ) => console . log ( paw , previousPaw ) ,
)
// Subscribe also supports an optional equality function
const unsub4 = useDogStore . subscribe (
( state ) => [ state . paw , state . fur ] ,
console . log ,
{ equalityFn : shallow } ,
)
// Subscribe and fire immediately
const unsub5 = useDogStore . subscribe ( ( state ) => state . paw , console . log , {
fireImmediately : true ,
} ) Der Zustand -Kern kann ohne die Reaktionsabhängigkeit importiert und verwendet werden. Der einzige Unterschied besteht darin, dass die Erstellungsfunktion keinen Haken, sondern die API -Dienstprogramme zurückgibt.
import { createStore } from 'zustand/vanilla'
const store = createStore ( ( set ) => ... )
const { getState , setState , subscribe , getInitialState } = store
export default store Sie können einen Vanillegeschäft mit useStore -Hook verwenden, der seit V4 verfügbar ist.
import { useStore } from 'zustand'
import { vanillaStore } from './vanillaStore'
const useBoundStore = ( selector ) => useStore ( vanillaStore , selector )set oder get -SETTEN nicht angewendet werden, um getState und setState zu erhalten.
Die Abonnementfunktion ermöglicht es Komponenten, sich an eine Zustandsverteilung zu binden, ohne Änderungen erneut zu erzwingen. Beste kombinieren Sie es mit Verwendung von Effect für automatische Abmeldungen bei Unmonto. Dies kann eine drastische Leistung beeinflussen, wenn Sie die Ansicht direkt mutieren dürfen.
const useScratchStore = create ( ( set ) => ( { scratches : 0 , ... } ) )
const Component = ( ) => {
// Fetch initial state
const scratchRef = useRef ( useScratchStore . getState ( ) . scratches )
// Connect to the store on mount, disconnect on unmount, catch state-changes in a reference
useEffect ( ( ) => useScratchStore . subscribe (
state => ( scratchRef . current = state . scratches )
) , [ ] )
. . . Die Reduzierung verschachtelter Strukturen ist lästig. Hast du Immer versucht?
import { produce } from 'immer'
const useLushStore = create ( ( set ) => ( {
lush : { forest : { contains : { a : 'bear' } } } ,
clearForest : ( ) =>
set (
produce ( ( state ) => {
state . lush . forest . contains = null
} ) ,
) ,
} ) )
const clearForest = useLushStore ( ( state ) => state . clearForest )
clearForest ( )Alternativ gibt es einige andere Lösungen.
Sie können die Daten Ihres Geschäfts mit jeder Art von Speicher bestehen.
import { create } from 'zustand'
import { persist , createJSONStorage } from 'zustand/middleware'
const useFishStore = create (
persist (
( set , get ) => ( {
fishes : 0 ,
addAFish : ( ) => set ( { fishes : get ( ) . fishes + 1 } ) ,
} ) ,
{
name : 'food-storage' , // name of the item in the storage (must be unique)
storage : createJSONStorage ( ( ) => sessionStorage ) , // (optional) by default, 'localStorage' is used
} ,
) ,
)Siehe die vollständige Dokumentation für diese Middleware.
Imerer ist auch als Middleware erhältlich.
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'
const useBeeStore = create (
immer ( ( set ) => ( {
bees : 0 ,
addBees : ( by ) =>
set ( ( state ) => {
state . bees += by
} ) ,
} ) ) ,
) const types = { increase : 'INCREASE' , decrease : 'DECREASE' }
const reducer = ( state , { type , by = 1 } ) => {
switch ( type ) {
case types . increase :
return { grumpiness : state . grumpiness + by }
case types . decrease :
return { grumpiness : state . grumpiness - by }
}
}
const useGrumpyStore = create ( ( set ) => ( {
grumpiness : 0 ,
dispatch : ( args ) => set ( ( state ) => reducer ( state , args ) ) ,
} ) )
const dispatch = useGrumpyStore ( ( state ) => state . dispatch )
dispatch ( { type : types . increase , by : 2 } )Oder benutzen Sie einfach unsere Redux-Middleware. Es wird Ihren Main-Reduder angestellt, den Anfangszustand festgelegt und dem Zustand selbst und der Vanille-API eine Versandfunktion hinzugefügt.
import { redux } from 'zustand/middleware'
const useGrumpyStore = create ( redux ( reducer , initialState ) ) Installieren Sie die Redux Devtools Chrome -Erweiterung, um die Devtools Middleware zu verwenden.
import { devtools } from 'zustand/middleware'
// Usage with a plain action store, it will log actions as "setState"
const usePlainStore = create ( devtools ( ( set ) => ... ) )
// Usage with a redux store, it will log full action types
const useReduxStore = create ( devtools ( redux ( reducer , initialState ) ) )Eine Redux Devtools -Verbindung für mehrere Geschäfte
import { devtools } from 'zustand/middleware'
// Usage with a plain action store, it will log actions as "setState"
const usePlainStore1 = create ( devtools ( ( set ) => ... , { name , store : storeName1 } ) )
const usePlainStore2 = create ( devtools ( ( set ) => ... , { name , store : storeName2 } ) )
// Usage with a redux store, it will log full action types
const useReduxStore = create ( devtools ( redux ( reducer , initialState ) ) , , { name , store : storeName3 } )
const useReduxStore = create ( devtools ( redux ( reducer , initialState ) ) , , { name , store : storeName4 } )Das Zuweisen verschiedener Verbindungsnamen trennen Speicher in Redux Devtools. Dies hilft auch dabei, verschiedene Geschäfte in separate Redux Devtools -Verbindungen zu gruppieren.
Devtools nimmt die Speicherfunktion als erstes Argument an. Optional können Sie die Speicheroptionen mit einem zweiten Argument benennen oder konfigurieren.
Name Store: devtools(..., {name: "MyStore"}) , das eine separate Instanz mit dem Namen "Mystore" in den Devtools erstellt.
Serialize -Optionen: devtools(..., { serialize: { options: true } }) .
Devtools loget nur in einem typischen kombinierten Reduzierer -Redux -Speicher von jedem getrennten Speicher aus. Sehen Sie einen Ansatz zur Kombination von Geschäften #163
Sie können einen bestimmten Aktionsart für jede set Funktion protokollieren, indem Sie einen dritten Parameter übergeben:
const useBearStore = create ( devtools ( ( set ) => ( {
...
eatFish : ( ) = > set (
( prev ) => ( { fishes : prev . fishes > 1 ? prev . fishes - 1 : 0 } ) ,
undefined ,
'bear/eatFish'
) ,
...Sie können auch den Typ der Aktion zusammen mit der Nutzlast protokollieren:
...
addFishes : ( count ) => set (
( prev ) => ( { fishes : prev . fishes + count } ) ,
undefined ,
{ type : 'bear/addFishes' , count , }
) ,
... Wenn kein Aktionstyp bereitgestellt wird, ist er standardmäßig mit "anonym". Sie können diesen Standardwert anpassen, indem Sie einen Parameter anonymousActionType bereitstellen:
devtools ( ... , { anonymousActionType : 'unknown' , ... } ) Wenn Sie Devtools deaktivieren möchten (beispielsweise bei der Produktion). Sie können diese Einstellung anpassen, indem Sie den enabled Parameter bereitstellen:
devtools ( ... , { enabled : false , ... } ) Der mit create erstellte Geschäft erfordert keine Kontextanbieter. In einigen Fällen möchten Sie möglicherweise Kontexte für die Abhängigkeitsinjektion verwenden oder wenn Sie Ihr Geschäft mit Requisiten von einer Komponente initialisieren möchten. Da es sich bei dem normalen Speicher um einen Haken handelt, kann es gegen die Regeln der Haken verletzen.
Die empfohlene Methode, die seit V4 erhältlich ist, ist die Verwendung des Vanille -Store.
import { createContext , useContext } from 'react'
import { createStore , useStore } from 'zustand'
const store = createStore ( ... ) // vanilla store without hooks
const StoreContext = createContext ( )
const App = ( ) => (
< StoreContext . Provider value = { store } >
...
</ StoreContext . Provider >
)
const Component = ( ) => {
const store = useContext ( StoreContext )
const slice = useStore ( store , selector )
. . . Grundlegendes TypeScript -Gebrauch erfordert nichts Besonderes, außer für das Schreiben create<State>()(...) anstelle von create(...) ...
import { create } from 'zustand'
import { devtools , persist } from 'zustand/middleware'
import type { } from '@redux-devtools/extension' // required for devtools typing
interface BearState {
bears : number
increase : ( by : number ) => void
}
const useBearStore = create < BearState > ( ) (
devtools (
persist (
( set ) => ( {
bears : 0 ,
increase : ( by ) => set ( ( state ) => ( { bears : state . bears + by } ) ) ,
} ) ,
{
name : 'bear-storage' ,
} ,
) ,
) ,
)Ein vollständigeres Typenschriftenhandbuch finden Sie hier.
Einige Benutzer möchten möglicherweise das Feature-Set von Zustand erweitern, das mit Bibliotheken von Drittanbietern von der Community erfolgen kann. Informationen zu Bibliotheken von Drittanbietern mit Zustand finden Sie im DOC.