
使用简化的通量原理的小型,快速且可扩展的国家管理解决方案。具有基于钩子的舒适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。