Zustand
zustand是一个react的状态管理库,可以替代context、redux使用。
创建Store
set用于修改store属性,get获取store内的其他属性。
interface Store {
bears: number;
cats: number;
doubleBears: () => number;
addOneBear: () => void;
addOneCat: () => void;
}
const useBearStore = create<Store>((set, get) => ({
bears: 0,
cats: 0,
doubleBears: () => {
return get().bears * 2;
},
addOneBear: () =>
set((state) => ({
bears: state.bears + 1,
})),
addOneCat: () =>
set((state) => ({
cats: state.cats + 1,
})),
}));
使用Store
获取所有state
// 任意 state 发生变化时,都会触发组件的重新渲染
function BearCounter() {
const { bears } = useBearStore();
return <h1>{bears} around here ...</h1>;
}
这样做的问题是,任意state发生变化时,都会触发组件的重新渲染,这可能会导致性能问题。
选择单个state
// 只有在 cats 变化时组件才会重新渲染。
function Cat() {
const cats = useBearStore((state) => state.cats);
return <h1>cats: {cats}</h1>;
}
在默认情况下,它使用严格相等(old === new)检测更改。这种检测方式对于基本数据类型的state选择是有效的。
组合多个state
如果需要构件由多个状态组成的引用数据类型(例如object、array),需要使用zustand/shallow提供的浅相等函数来进行浅层差异比较。
// 任意 state 发生变化时,都会触发组件的重新渲染
function Cat() {
const { cats } = useBearStore((state) => ({ cats: state.cats }));
return <h1>cats: {cats}</h1>;
}
// 只有在 cats 变化时组件才会重新渲染。
function Cat() {
const { cats } = useBearStore((state) => ({ cats: state.cats }), shallow);
console.log("Cats", cats);
return <h1>cats: {cats}</h1>;
}
shallow 函数的实现原理是通过遍历对象的每个属性值,如果某个属性值是对象,则递归调用 shallow 函数进行浅层比较,如果是其他类型的值,则使用 Object.is
进行比较。由于 Object.is
比较的是两个值是否相等,包括严格相等和同值相等,因此可以准确地判断值是否发生了变化。
当然也可以自己自定义比较函数。
const treats = useBearStore(
(state) => state.treats,
(oldTreats, newTreats) => compare(oldTreats, newTreats)
);
衍生属性
- zustand 用了类似 redux selector 的方法,实现相应的状态派生。
- 可以实现只有监听的属性变化,才会重新渲染
const doubleBears = (s: Store) => {
return s.bears * 2;
}
function Bear() {
const count = useBearStore(doubleBears, shallow)
return <h1>Bear:{count}</h1>;
}
修改Store
覆盖Store
set 函数有一个默认为 false 的第二个参数。如果设置为 true,它会替换整个store而不是merge state。
const useBearStore = create<Store>((set, get) => ({
bears: 0,
cats: 0,
doubleBears: () => {
return get().bears * 2;
},
addOneBear: () =>
set((state) => ({
bears: state.bears + 1,
})),
addOneCat: () =>
set(
(state) => ({
cats: state.cats + 1,
}),
true // 修改后只剩下 store 中只有 cats
),
}));
异步操作
调用set。
const useBearStore = create<Store>((set, get) => ({
bears: 0,
getBears: (n: number) => {
setTimeout(() => {
set({ bears: n })
}, 2000)
}
}));
在actions中获取state
- 通过回调函数
const useBearStore = create<Store>((set, get) => ({
bears: 0,
addOneBear: () => set(state => ({ bears: state.bears + 1 })),
}));
- 通过get获取
const useBearStore = create<Store>((set, get) => ({
bears: 0,
addOneBear: () => set({ bears: get().bears + 1 }),
}));
瞬时更新(针对经常发生的状态更改)
如果需要改变状态,而不期望重新渲染可以考虑使用订阅功能来实现,最好与useEffect结合使用,在卸载时自动取消订阅。
const useScratchStore = create(set => ({ scratches: 0, ... }))
const Component = () => {
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)
), [])
...
组件外使用
获取、修改
// 获取state
const bears = useBearStore.getState().bears
// 修改state
useBearStore.setState({ bears: 1 })
订阅
// 监听所有更改,在每次更改时同步触发
const unSub = useBearStore.subscribe(console.log)
// 取消订阅
unSub()
如果需要选择订阅,需要使用subscribeWithSelector
中间件。
subscribe(selector, callback, options?: { equalityFn, fireImmediately }): Unsubscribe
举例
import { subscribeWithSelector } from 'zustand/middleware'
import { create } from 'zustand';
interface Store {
bears: number;
addOneBear: () => void;
}
const useBearStore = create(
subscribeWithSelector<Store>((set, get) => ({
bears: 0,
addOneBear: () => set({ bears: get().bears + 1 }),
}))
)
// 监听bear的变化
const unsub = useBearStore.subscribe(
(state) => state.bears,
(bears, previousBears) => console.log(bears, previousBears)
)
// 支持可选的比较函数。
const unsub2 = useBearStore.subscribe(
(state) => [state.bear, state.fur],
console.log,
{ equalityFn: shallow }
)
// 立即订阅并触发(执行)
const unsub3 = useBearStore.subscribe((state) => state.bear, console.log, {
fireImmediately: true,
})
脱离react使用
Zustand核心可以在没有React依赖的情况下导入和使用。唯一的区别是create函数不返回hook,而是API。
import { createStore } from 'zustand/vanilla'
const store = createStore(() => ({ ... }))
const { getState, setState, subscribe } = store
export default store
你可以使用自v4版本起提供的useStore钩子来使用Vanilla Store。
import { useStore } from 'zustand'
import { vanillaStore } from './vanillaStore'
const useBoundStore = (selector) => useStore(vanillaStore, selector)
中间件
自定义中间件
可以扩展zustand功能。一个完整例子:
// Log every time state is changed
import { StateCreator } from 'zustand';
type LogImpl = <T>(
storeInitializer: StateCreator<T, [], []>,
) => StateCreator<T, [], []>;
// Log every time state is changed
const log: LogImpl = config => (set, get, api) => {
console.log(api);
return config(
(...args) => {
console.log(' applying', args);
set(...args);
console.log(' new state', get());
},
get,
api,
);
};
export default log;
const useBeeStore = create(
log((set) => ({
bees: false,
setBees: (input) => set({ bees: input }),
}))
)
持久化中间件
可以根据需要来持久化state
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', // unique name
storage: createJSONStorage(() => sessionStorage), // (optional) by default, 'localStorage' is used
}
)
)
See the full documentation for this middleware.
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
}),
}))
)
redux中间件
使用类redux写法
import { create } from 'zustand'
import { redux } from 'zustand/middleware'
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(redux(reducer, initialState))
Redux devtools
可以支持在redux detools中调试
import { devtools } from 'zustand/middleware'
// Usage with a plain action store, it will log actions as "setState"
const usePlainStore1 = create(devtools(store), { name, store: storeName1 })
const usePlainStore2 = create(devtools(store), { 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 })
React context
使用 create 创建的 store 本身不需要Context。但在某些情况下,您可能希望使用context进行依赖注入,可以参考例子:
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)
...