
Una solución de gestión estatal pequeña, rápida y escalable que utiliza principios de flujo simplificados. Tiene una API cómoda basada en ganchos, no es Boilerplatey o obstinado.
No lo ignores porque es lindo. Tiene bastante garras, se dedicó mucho tiempo a tratar con dificultades comunes, como el temido problema de los niños zombies, reaccionar la concurrencia y la pérdida de contexto entre los renderistas mixtos. Puede ser el único gerente estatal en el espacio React que hace todo esto correcto.
Puedes probar una demostración en vivo aquí.
npm install zustand ¡Tu tienda es un gancho! Puede poner cualquier cosa: primitivas, objetos, funciones. El estado debe actualizarse de manera inmutable y la función set fusiona el estado para ayudarlo.
import { create } from 'zustand'
const useBearStore = create ( ( set ) => ( {
bears : 0 ,
increasePopulation : ( ) => set ( ( state ) => ( { bears : state . bears + 1 } ) ) ,
removeAllBears : ( ) => set ( { bears : 0 } ) ,
} ) ) Use el gancho en cualquier lugar, no se necesitan proveedores. Seleccione su estado y el componente volverá a renderizar los cambios.
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 >
}¡Puede, pero tenga en cuenta que hará que el componente se actualice sobre cada cambio de estado!
const state = useBearStore ( ) Detecta cambios con la igualdad estricta (antigua === nueva) Por defecto, esto es eficiente para las selecciones de estado atómico.
const nuts = useBearStore ( ( state ) => state . nuts )
const honey = useBearStore ( ( state ) => state . honey )Si desea construir un solo objeto con múltiples picks estatales en el interior, similar a MapStateProps de Redux, puede usar UseShallow para evitar que los resonantes innecesarios cuando la salida del selector no cambie de acuerdo con la igualdad superficial.
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 ) ) ) Para obtener más control sobre la re-renderización, puede proporcionar cualquier función de igualdad personalizada (este ejemplo requiere el uso de createWithEqualityFn ).
const treats = useBearStore (
( state ) => state . treats ,
( oldTreats , newTreats ) => compare ( oldTreats , newTreats ) ,
) La función set tiene un segundo argumento, false por defecto. En lugar de fusionarse, reemplazará el modelo de estado. Tenga cuidado de no eliminar las piezas en las que confía, como las acciones.
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 ) ,
} ) ) Simplemente llame set cuando esté listo, a Zustand no le importa si sus acciones son asíncronas o no.
const useFishStore = create ( ( set ) => ( {
fishies : { } ,
fetch : async ( pond ) => {
const response = await fetch ( pond )
set ( { fishies : await response . json ( ) } )
} ,
} ) ) set permite set(state => result) , pero aún tiene acceso al estado fuera de él a través de get .
const useSoundStore = create ( ( set , get ) => ( {
sound : 'grunt' ,
action : ( ) => {
const sound = get ( ) . sound
. . . A veces necesita acceder al estado de manera no reactiva o actuar en la tienda. Para estos casos, el gancho resultante tiene funciones de utilidad adjuntas a su prototipo.
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 )
. . . Si necesita suscribirse con un selector, subscribeWithSelector Middleware ayudará.
Con esta subscribe middleware acepta una firma adicional:
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 ,
} ) Zustand Core se puede importar y utilizar sin la dependencia de React. La única diferencia es que la función Crear no devuelve un gancho, sino las utilidades API.
import { createStore } from 'zustand/vanilla'
const store = createStore ( ( set ) => ... )
const { getState , setState , subscribe , getInitialState } = store
export default store Puede usar una tienda de vainilla con el gancho useStore disponible desde V4.
import { useStore } from 'zustand'
import { vanillaStore } from './vanillaStore'
const useBoundStore = ( selector ) => useStore ( vanillaStore , selector )set o get no se aplican a getState y setState .
La función de suscripción permite que los componentes se unan a una porción estatal sin obligar a reiniciar los cambios. Lo mejor es combínelo con UseeFectect para cancelar la suscripción automática en uno de los montones. Esto puede tener un impacto drástico en el rendimiento cuando se le permite mutar la vista directamente.
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 )
) , [ ] )
. . . Reducir estructuras anidadas es agotador. ¿Has probado Immer?
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 ( )Alternativamente, hay otras soluciones.
Puede persistir los datos de su tienda utilizando cualquier tipo de almacenamiento.
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
} ,
) ,
)Vea la documentación completa para este middleware.
Immer también está disponible como middleware.
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 } )O simplemente use nuestro Redux-Middleware. Almla su reducor principal, establece el estado inicial y agrega una función de despacho al estado mismo y a la API de vainilla.
import { redux } from 'zustand/middleware'
const useGrumpyStore = create ( redux ( reducer , initialState ) ) Instale la extensión Redux DevTools Chrome para usar el middleware DevTools.
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 ) ) )Una conexión de Redux DevTools para múltiples tiendas
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 } )La asignación de diferentes nombres de conexión separará las tiendas en Redux DevTools. Esto también ayuda a agrupar diferentes tiendas en conexiones Redux DevTools separadas.
DevTools toma la función de la tienda como su primer argumento, opcionalmente puede nombrar la tienda o configurar las opciones de serialización con un segundo argumento.
Nombre de la tienda: devtools(..., {name: "MyStore"}) , que creará una instancia separada llamada "MyStore" en DevTools.
Opciones de Serialize: devtools(..., { serialize: { options: true } }) .
DevTools solo registrará las acciones de cada tienda separada a diferencia de una tienda Redux de reductores combinados típicos. Vea un enfoque para combinar las tiendas #163
Puede registrar un tipo de acción específico para cada función set pasando un tercer parámetro:
const useBearStore = create ( devtools ( ( set ) => ( {
...
eatFish : ( ) = > set (
( prev ) => ( { fishes : prev . fishes > 1 ? prev . fishes - 1 : 0 } ) ,
undefined ,
'bear/eatFish'
) ,
...También puede registrar el tipo de acción junto con su carga útil:
...
addFishes : ( count ) => set (
( prev ) => ( { fishes : prev . fishes + count } ) ,
undefined ,
{ type : 'bear/addFishes' , count , }
) ,
... Si no se proporciona un tipo de acción, está predeterminado a "Anónimo". Puede personalizar este valor predeterminado proporcionando un parámetro anonymousActionType :
devtools ( ... , { anonymousActionType : 'unknown' , ... } ) Si desea deshabilitar DevTools (en producción, por ejemplo). Puede personalizar esta configuración proporcionando el parámetro enabled :
devtools ( ... , { enabled : false , ... } ) La tienda creada con create no requiere proveedores de contexto. En algunos casos, es posible que desee usar contextos para la inyección de dependencia o si desea inicializar su tienda con accesorios de un componente. Debido a que la tienda normal es un gancho, pasarlo como un valor de contexto normal puede violar las reglas de los ganchos.
El método recomendado disponible ya que V4 es usar la tienda de vainilla.
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 )
. . . El uso básico de TypeScript no requiere nada especial excepto para escribir create<State>()(...) en lugar de 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' ,
} ,
) ,
) ,
)Aquí hay una guía mecanografiada más completa.
Algunos usuarios pueden querer extender el conjunto de funciones de Zustand que se puede hacer utilizando bibliotecas de terceros hechas por la comunidad. Para obtener información sobre bibliotecas de terceros con Zustand, visite el documento.