Zustand 使用优化:关于自动生成选择器

Zustand 是目前 React 生态里比较受欢迎的一个状态库,主要是因为用法上的简洁。

Zustand 简单使用

首先安装 zustand:

bash 复制代码
# NPM
npm install zustand
# Or, use any package manager of your choice.

接着从 zustand 库中引入 create API 就能创建同时包含状态和用于修改状态的方法的 Store 对象了。

jsx 复制代码
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() 了。

你可以引入状态:

jsx 复制代码
function BearCounter() {
  const bears = useBearStore((state) => state.bears)
  return <h1>{bears} around here...</h1>
}

也可以引入用于修改状态的方法:

jsx 复制代码
function Controls() {
  const increasePopulation = useBearStore((state) => state.increasePopulation)
  return <button onClick={increasePopulation}>one up</button>
}

如此一来,你在 <Controls /> 中调用修改 increasePopulation() 后,会触发 state.bears 的值加 1,接着就能在 <BearCounter /> 中看到新的值了。

掌握了以上关于 Zustand 的基本用法后,其实你就可以开发项目了。

不过,你想更进一步提升开发体验,那么就要去解决这个过程当中的一些使用痛点。

其中一个是关于更新嵌套状态的,这在之前的文章《React 状态库 Zustand 入门教程》 中有提到,有兴趣的读者可以移步阅读。

不过,我们本次关注的是另一个痛点:就是从 Store 中获取状态/Action的过程。

自动生成选择器

按照之前的介绍,在创建完 Store 之后,我们每次都要在组件中这样去使用:

jsx 复制代码
const bears = useBearStore((state) => state.bears)

我们要使用这种方式从 Store 中提取状态或是 Action。

不过每次频繁这样去写这样一个选择器函数是很乏味的,这个时候我们就可以考虑借助一个工具函数,对我们的 Store 进行增强,支持状态/Action的快捷访问。

这就是我们要介绍的 createSelectors(store) 函数了------先亮代码:

jsx 复制代码
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。

diff 复制代码
- const useBearStore = create((set) => ({
+ const useBearStoreBase = create((set) => ({
  // ...
}))

+ const useBearStore = createSelectors(useBearStoreBase)

现在,修改组件中使用 useBearStore 的地方。

diff 复制代码
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 onClick={increasePopulation}>one up</button>
}

减少了一些代码量,但是积少成多,也会提升一些开发体验。

不过需要注意的是,每次使用 .use 获取不管是状态还是 Acton 时,都要带上 () 的调用后缀。

jsx 复制代码
// 不管是状态还是 Action,后面都要带上 `()`
const bears = useBearStore.use.bears()
const increasePopulation = useBearStore.use.increasePopulation()

当然,现在项目中大都使用 TypeScript,为了获得更好的类型提示,我们对 createSelectors() 进行改造,添加类型注解。

ts 复制代码
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
}

这也是官方给出的方案,如果你对 TypeScript 不够熟悉也没关系,直接将上述代码贴到项目中使用即可。

总结

本文我们讲解了在使用 Zustand 时的一个小优化,关于自动生成选择器。借助 createSelectors(),我们可以更加轻松、快捷的访问 Store 中的状态或是 Action。

好了,希望本文的内容对你的工作有所帮助。感谢阅读,再见。

相关推荐
Novlan11 天前
@tdesign/uniapp 常见问题
前端
sww_10261 天前
SAA ReactAgent工作原理
开发语言·前端·javascript
linux_cfan1 天前
拒绝“黑屏”与“哑剧”:Web视频播放器UX体验与自动播放选型指南 (2026版)
前端·javascript·音视频·html5·ux
小庄梦蝶1 天前
宝塔使用nodejs管理器下载nodejs版本失败解决方式之一
linux·运维·前端
be or not to be1 天前
假期js学习汇总
前端·javascript·学习
SuperEugene1 天前
日期与时间处理:不用库和用 dayjs 的两种思路
前端·javascript
菜鸟小芯1 天前
【GLM-5 陪练式前端新手入门】第一篇:从 GLM-5 提示到实践,完成前端入门第一步
前端·人工智能
木斯佳1 天前
前端八股文面经大全:字节跳动交易与广告前端一面(2026-2-10)·面经深度解析
前端
Highcharts.js1 天前
如何根据派生数据创建钟形曲线图表?highcharts正态分布曲线使用指南:从创建到设置一文搞定
开发语言·javascript·开发文档·正态分布·highcharts·图表类型·钟形图
a1117761 天前
纸张生成器(html开源)
前端·开源·html