
使用簡化的通量原理的小型,快速且可擴展的國家管理解決方案。具有基於鉤子的舒適API,不是樣板或有用的。
不要忽略它,因為它很可愛。它具有相當的爪子,花了很多時間來處理常見的陷阱,例如可怕的殭屍兒童問題,反應並發性以及混合渲染器之間的背景損失。這可能是React空間中所有這些正確的國家經理。
您可以在這裡嘗試實時演示。
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 )如果要在內部使用多個狀態挑選的單個對象(類似於Redux的MapStateToprops),則可以使用Useshlower來防止不必要的重新啟動器,而當選擇器輸出不會根據淺層相等而變化。
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中間件將有所幫助。
使用此中間件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。唯一的區別是,創建函數不會返回鉤子,而是API實用程序。
import { createStore } from 'zustand/vanilla'
const store = createStore ( ( set ) => ... )
const { getState , setState , subscribe , getInitialState } = store
export default store您可以使用自V4以來可用的帶有useStore掛鉤的香草商店。
import { useStore } from 'zustand'
import { vanillaStore } from './vanillaStore'
const useBoundStore = ( selector ) => useStore ( vanillaStore , selector )set或get中間件不應用於getState和setState 。
訂閱功能允許組件綁定到狀態段,而不會強迫重新渲染更改。最好將其與自動取消訂閱的使用效率結合起來。當允許您直接突變視圖時,這會產生巨大的性能影響。
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
} ,
) ,
)請參閱此中間件的完整文檔。
浸入也可以作為中間件可用。
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將存儲函數作為其第一個參數,您可以選擇地將商店命名或使用第二個參數配置序列化選項。
名稱存儲: devtools(..., {name: "MyStore"}) ,它將在DevTools中創建一個名為“ Mystore”的實例。
序列化選項: devtools(..., { serialize: { options: true } }) 。
DevTools僅在典型的組合還原redux Store中,只能從每個分離的商店記錄動作。查看結合商店#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以來,推薦的方法是使用香草店。
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 )
. . . 基本的TypeScript用法不需要任何特殊的東西,除了編寫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。