Zustand[1] 是目前 React 生态里比较受欢迎的一个状态库,主要是因为用法上的简洁。
Zustand 简单使用
首先安装 zustand:
# NPM
npm install zustand
# Or, use any package manager of your choice.
接着从 zustand 库中引入 create API 就能创建同时包含状态和用于修改状态的方法的 Store 对象了。
import { create } from 'zustand'
const useBearStore = create((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
updateBears: (newBears) => set({ bears: newBears }),
}))
这里,create() 接受一个回调函数用于定义初始 Store 中包含的内容:
- bears 是状态
- increasePopulation()、removeAllBears()、updateBears() 则是用于修改 bears 这个状态的方法,又叫 Action
同时,create() 返回的 useStore() 是一个 React Hook。useBearStore() 接收的是一个用于从 Store 中提取内容的回调函数,又叫“选择器(Selector)”。
接下来,就可以在你的组件中使用 useBearStore() 了。
你可以引入状态:
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>
}
如此一来,你在 <Controls /> 中调用修改 increasePopulation() 后,会触发 state.bears 的值加 1,接着就能在 <BearCounter /> 中看到新的值了。
图片
掌握了以上关于 Zustand 的基本用法后,其实你就可以开发项目了。
不过,你想更进一步提升开发体验,那么就要去解决这个过程当中的一些使用痛点。
其中一个是关于更新嵌套状态的,这在之前的文章《React 状态库 Zustand 入门教程》[2] 中有提到,有兴趣的读者可以移步阅读。
不过,我们本次关注的是另一个痛点:就是从 Store 中获取状态/Action的过程。
自动生成选择器
按照之前的介绍,在创建完 Store 之后,我们每次都要在组件中这样去使用:
const bears = useBearStore((state) => state.bears)
我们要使用这种方式从 Store 中提取状态或是 Action。
不过每次频繁这样去写这样一个选择器函数是很乏味的,这个时候我们就可以考虑借助一个工具函数,对我们的 Store 进行增强,支持状态/Action的快捷访问。
这就是我们要介绍的 createSelectors(store) 函数了——先亮代码:
const createSelectors = (store) => {
store.use = {}
for (let k of Object.keys(store.getState())) {
store.use[k] = () => store((s) => s[k])
}
return store
}
代码量并不多,也好理解。
createSelectors() 接收的 store 就是前一节的 useBearStore,也就是 create() 的返回值。
createSelectors() 的作用很简单,就是向 store 中添加一个 .use 属性,用于快捷访问其上的内容。
当然,这里有一个隐藏的点,就是可以通过 store.getState() 拿到当前 Store 的所有内容。
图片
接着,修改之前的内容——为了便于区分,我们将原来的 useBearStore 该名称 useBearStoreBase 了。经 createSelectors() 处理后,返回的是 useBearStore。
- const useBearStore = create((set) => ({
+ const useBearStoreBase = create((set) => ({
// ...
}))
+ const useBearStore = createSelectors(useBearStoreBase)
现在,修改组件中使用 useBearStore 的地方。
function BearCounter() {
- const bears = useBearStore((state) => state.bears)
+ const bears = useBearStore.use.bears()
return <h1>{bears} around here...</h1>
}
function Controls() {
- const increasePopulation = useBearStore((state) => state.increasePopulation)
+ const increasePopulation = useBearStore.use.increasePopulation()
return <button notallow={increasePopulation}>one up</button>
}
减少了一些代码量,但是积少成多,也会提升一些开发体验。
图片
不过需要注意的是,每次使用 .use 获取不管是状态还是 Acton 时,都要带上 () 的调用后缀。
// 不管是状态还是 Action,后面都要带上 `()`
const bears = useBearStore.use.bears()
const increasePopulation = useBearStore.use.increasePopulation()
当然,现在项目中大都使用 TypeScript,为了获得更好的类型提示,我们对 createSelectors() 进行改造,添加类型注解。
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
}
这也是官方给出的方案[3],如果你对 TypeScript 不够熟悉也没关系,直接将上述代码贴到项目中使用即可。
总结
本文我们讲解了在使用 Zustand 时的一个小优化,关于自动生成选择器。借助 createSelectors(),我们可以更加轻松、快捷的访问 Store 中的状态或是 Action。
好了,希望本文的内容对你的工作有所帮助。感谢阅读,再见。
参考资料
[1]Zustand: https://docs.pmnd.rs/zustand
[2]《React 状态库 Zustand 入门教程》: https://juejin.cn/post/7388064351504056335#heading-4
[3]官方给出的方案: https://docs.pmnd.rs/zustand/guides/auto-generating-selectors#create-the-following-function:-createselectors