
Небольшое, быстрое и масштабируемое решение для управления состоянием Bearbones с использованием упрощенных принципов потока. Имеет удобный API, основанный на крючках, не является Cowerplatey или самоуверенными.
Не игнорируйте это, потому что это мило. У него довольно когти, было потрачено много времени на общие ошибки, такие как страшная проблема с детьми зомби, реагирование параллельности и потери контекста между смешанными визуализаторами. Это может быть единственный государственный менеджер в пространстве реагирования, который получает все это право.
Вы можете попробовать живую демонстрацию здесь.
npm install zustand Ваш магазин - крюк! Вы можете поместить в это все: примитивы, объекты, функции. Состояние должно быть обновляется непостоянно, а функция set объединяет состояние, чтобы помочь ему.
import { create } from 'zustand'
const useBearStore = create ( ( set ) => ( {
bears : 0 ,
increasePopulation : ( ) => set ( ( state ) => ( { bears : state . bears + 1 } ) ) ,
removeAllBears : ( ) => set ( { bears : 0 } ) ,
} ) ) Используйте крюк в любом месте, никаких поставщиков не требуется. Выберите свое состояние, и компонент будет повторно рендеринг при изменениях.
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 >
}Вы можете, но имейте в виду, что это заставит компонент обновлять каждое изменение состояния!
const state = useBearStore ( ) Он обнаруживает изменения с строгим равенством (старым === Новым) по умолчанию, это эффективно для выборов атомного состояния.
const nuts = useBearStore ( ( state ) => state . nuts )
const honey = useBearStore ( ( state ) => state . honey )Если вы хотите построить один объект с несколькими состояниями, похожими на MapStateToprops Redux, вы можете использовать UseShalling, чтобы предотвратить ненужные ререндеры, когда вывод селектора не изменяется в соответствии с мелким равным.
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 ) ) ) Для получения дополнительной контроля над повторным использованием вы можете предоставить любую пользовательскую функцию равенства (этот пример требует использования createWithEqualityFn ).
const treats = useBearStore (
( state ) => state . treats ,
( oldTreats , newTreats ) => compare ( oldTreats , newTreats ) ,
) Функция set имеет второй аргумент, false по умолчанию. Вместо того, чтобы слияние, это заменит модель состояния. Будьте осторожны, чтобы не уничтожить части, на которые вы полагаетесь, как действия.
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 ) ,
} ) ) Просто set когда вы будете готовы, Zustand не волнует, являются ли ваши действия асинхронными или нет.
const useFishStore = create ( ( set ) => ( {
fishies : { } ,
fetch : async ( pond ) => {
const response = await fetch ( pond )
set ( { fishies : await response . json ( ) } )
} ,
} ) ) set позволяет Fn-Updates set(state => result) , но у вас все еще есть доступ к состоянию вне его через get .
const useSoundStore = create ( ( set , get ) => ( {
sound : 'grunt' ,
action : ( ) => {
const sound = get ( ) . sound
. . . Иногда вам нужно получить доступ к состоянию нереактивным образом или действовать в магазине. Для этих случаев, полученный крючок имеет коммунальные функции, прикрепленные к его прототипу.
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 )
. . . Если вам нужно подписаться с селектором, поможет subscribeWithSelector Middleware.
С этой subscribe промежуточной программы принимает дополнительную подпись:
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 может быть импортирован и использован без зависимости React. Единственное отличие состоит в том, что функция создания не возвращает крючок, а утилиты API.
import { createStore } from 'zustand/vanilla'
const store = createStore ( ( set ) => ... )
const { getState , setState , subscribe , getInitialState } = store
export default store Вы можете использовать ванильный магазин с useStore Hook, доступным с V4.
import { useStore } from 'zustand'
import { vanillaStore } from './vanillaStore'
const useBoundStore = ( selector ) => useStore ( vanillaStore , selector )set или get не применяются для getState и setState .
Функция подписки позволяет компонентам связываться с работой состояния без принуждения к повторному рендеринге при изменениях. Лучше всего объединить его с помощью использования для автоматического отказа от подписки на Unmount. Это может оказать резкое влияние на производительность, когда вам разрешено непосредственно мутировать.
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 )
) , [ ] )
. . . Сокращение вложенных структур утомительно. Вы пробовали погрузить?
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 ( )В качестве альтернативы, есть другие решения.
Вы можете сохранить данные вашего магазина, используя любые виды хранилища.
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
} ,
) ,
)Смотрите полную документацию для этого промежуточного программного обеспечения.
Immer также доступен в качестве промежуточного программного обеспечения.
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 } )Или просто используйте наше Redux-Middleware. Он поднимает ваш основной редуктор, устанавливает начальное состояние и добавляет функцию отправки в само государство и ванильный API.
import { redux } from 'zustand/middleware'
const useGrumpyStore = create ( redux ( reducer , initialState ) ) Установите расширение Redux Devtools Chrome, чтобы использовать промежуточное программное обеспечение 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 ) ) )Одно соединение Redux Devtools для нескольких магазинов
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 } )Назначение различных имен подключений будет отделять хранилища в Redux Devtools. Это также помогает группировать различные магазины в отдельные соединения Redux Devtools.
Devtools воспринимает функцию магазина в качестве первого аргумента, необязательно вы можете назвать хранилище или настроить параметры Serialize со вторым аргументом.
Stame Store: devtools(..., {name: "MyStore"}) , который создаст отдельный экземпляр с именем "MySotore" в DevTools.
Serialize Options: devtools(..., { serialize: { options: true } }) .
Devtools будет регистрировать действия только из каждого отдельного хранилища, в отличие от типичного комбинированного магазина Redux. Смотрите подход к объединению магазинов #163
Вы можете зарегистрировать конкретный тип действия для каждой функции set , передавая третий параметр:
const useBearStore = create ( devtools ( ( set ) => ( {
...
eatFish : ( ) = > set (
( prev ) => ( { fishes : prev . fishes > 1 ? prev . fishes - 1 : 0 } ) ,
undefined ,
'bear/eatFish'
) ,
...Вы также можете зарегистрировать тип действия вместе с его полезной нагрузкой:
...
addFishes : ( count ) => set (
( prev ) => ( { fishes : prev . fishes + count } ) ,
undefined ,
{ type : 'bear/addFishes' , count , }
) ,
... Если тип действия не предоставлен, он не выполняется «анонимным». Вы можете настроить это значение по умолчанию, предоставив параметр anonymousActionType :
devtools ( ... , { anonymousActionType : 'unknown' , ... } ) Если вы хотите отключить DevTools (например, на производстве). Вы можете настроить этот параметр, предоставив параметр enabled :
devtools ( ... , { enabled : false , ... } ) Магазин, созданный с помощью create не требует поставщиков контекста. В некоторых случаях вы можете использовать контексты для введения зависимости или, если вы хотите инициализировать свой магазин с помощью реквизита из компонента. Поскольку нормальный магазин является крючком, передача его в качестве нормального значения контекста может нарушать правила крючков.
Рекомендуемый метод, доступный со времен V4 для использования Vanilla 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 )
. . . Базовое использование типовойписнойпис не требует ничего особенного, за исключением записи create<State>()(...) вместо 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' ,
} ,
) ,
) ,
)Более полное руководство по типографии здесь.
Некоторые пользователи могут захотеть расширить набор функций Zustand, который можно сделать, используя сторонние библиотеки, сделанные сообществом. Для получения информации о сторонних библиотеках с Zustand посетите DOC.