npm install zustand
store
是个hook
,create
函数的回调函数返回的值就是store
,可以存放原始值,对象,函数(相当于action
),其中回调函数的set参数默认是合并state
import { create } from 'zustand'
const useStore = create((set) => ({
bears: 0,// 这里是state
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),// 函数相当于action
removeAllBears: () => set({ bears: 0 }),
}))
组件中使用,useStore
接受一个函数参数,该函数称为selector函数,参数返回值作为要使用的state值或action
函数,当返回的值有变化时,使用到的组件才会更新,如果不传selector
函数,那么只要state
值发生变化,那么组件就会重新渲染
function BearCounter() {
const bears = useStore((state) => state.bears)
return <h1>{bears} around here...</h1>
}
function Controls() {
const increasePopulation = useStore((state) => state.increasePopulation)
return <button onClick={increasePopulation}>one up</button>
}
zustand通过set
函数更新state
,set
函数通过接受一个返回state
的函数,浅合并该state
const useStore = create<State & Action>((set) => ({
firstName: '',
lastName: '',
updateFirstName: (firstName) => set(() => ({ firstName: firstName })),
updateLastName: (lastName) => set(() => ({ lastName: lastName })),
}))
如果要更新嵌套深层的对象,那么需要层层解构赋值,比较麻烦。
normalInc: () =>
set((state) => ({
deep: {
...state.deep,
nested: {
...state.deep.nested,
obj: {
...state.deep.nested.obj,
count: state.deep.nested.obj.count + 1
}
}
}
})),
使用immer
库的produce
函数将set函数的函数参数包裹起来,就更方便了
immerInc: () =>
set(produce((state: State) => { ++state.deep.nested.obj.count })),
默认情况下,zustand
的set
函数会合并其函数参数返回的state
,如果想禁用这一行为,将返回的state作为新state而不合并,给set
函数传递第二个参数replaceFlag:true
就行
set((state) => newState, true)
store
,应用过大可以将store分成
几个 slice
,类似 vuex
里的 module
set
,或者 setState
更新仓库 store
useStore.setState((state)=>({count:state.count+1}))
increment:()=>set((state)=>({count:state.count+1}))
store
和 actions
放在一起const useBoundStore = create((set) => ({
storeSliceA: ...,
storeSliceB: ...,
storeSliceC: ...,
updateX: () => set(...),
updateY: () => set(...),
}))
createSelectors
函数selector
函数用于从store
中返回需要的state
和action
,每次都写一个这样的函数很繁琐,可以通过一个函数自动给useStore
添加selector
函数,该函数目的是在 useStore
上添加use
属性,通过useStore.use.xxx()
来返回所需state
和action
import { StoreApi, UseBoundStore } from 'zustand'
type WithSelectors<S> = S extends { getState: () => infer T }
? S & { use: { [K in keyof T]: () => T[K] } }
: never
const createSelectors = <S extends UseBoundStore<StoreApi<object>>>(
_store: S
) => {
let store = _store as WithSelectors<typeof _store>
store.use = {}
for (let k of Object.keys(store.getState())) {
;(store.use as any)[k] = () => store((s) => s[k as keyof typeof s])
}
return store
}
createSelectors
包裹 useStore
store
,使用 createSelectors
包裹 useStore
interface BearState {
bears: number
increase: (by: number) => void
increment: () => void
}
const useBearStoreBase = create<BearState>()((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
increment: () => set((state) => ({ bears: state.bears + 1 })),
}))
const useBearStore = createSelectors(useBearStoreBase)
通过 store.use.xxx()
,获取state
和action
// get the property
const bears = useBearStore.use.bears()
// get the action
const increment = useBearStore.use.increment()
使用 store.setState((state)=>{})
export const useBoundStore = create(() => ({
count: 0,
text: 'hello',
}))
export const inc = () =>
useBoundStore.setState((state) => ({ count: state.count + 1 }))
export const setText = (text) => useBoundStore.setState({ text })
create()()
create<State>()((set)=>({}))
import { create } from 'zustand'
interface BearState {
bears: number
increase: (by: number) => void
}
const useBearStore = create<BearState>()((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
}))
更新Map
和Set
需要新建一个Map
和Set
,主要是为了让 React 知道更新发生了
import { create } from 'zustand'
const useFooBar = create(() => ({ foo: new Map(), bar: new Set() }))
function doSomething() {
// doing something...
// If you want to update some React component that uses `useFooBar`, you have to call setState
// to let React know that an update happened.
// Following React's best practices, you should create a new Map/Set when updating them:
useFooBar.setState((prev) => ({
foo: new Map(prev.foo).set('newKey', 'newValue'),
bar: new Set(prev.bar).add('newKey'),
}))
}
import { create } from 'zustand'
export const createFishSlice = (set) => ({
fishes: 0,
addFish: () => set((state) => ({ fishes: state.fishes + 1 })),
})
export const createBearSlice = (set) => ({
bears: 0,
addBear: () => set((state) => ({ bears: state.bears + 1 })),
eatFish: () => set((state) => ({ fishes: state.fishes - 1 })),
})
export const useBoundStore = create((...a) => ({
...createBearSlice(...a),
...createFishSlice(...a),
}))
function App() {
const bears = useBoundStore((state) => state.bears)
const fishes = useBoundStore((state) => state.fishes)
const addBear = useBoundStore((state) => state.addBear)
return (
<div>
<h2>Number of bears: {bears}</h2>
<h2>Number of fishes: {fishes}</h2>
<button onClick={() => addBear()}>Add a bear</button>
</div>
)
}
export default App
import { create } from 'zustand'
import { createBearSlice } from './bearSlice'
import { createFishSlice } from './fishSlice'
import { persist } from 'zustand/middleware'
export const useBoundStore = create(
persist(
(...a) => ({
...createBearSlice(...a),
...createFishSlice(...a),
}),
{ name: 'bound-store' }
)
)