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。

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

相关推荐
行者-全栈开发26 分钟前
【前端/JS框架】CVE-2026-3854:GitHub Enterprise Server X-Stat注入XSS漏洞深度剖析与紧急修复指南
前端·javascript·github
计算机魔术师1 小时前
AMD砸82亿买下李飞飞,这次不卖芯片卖什么?
前端
hzxpaipai2 小时前
杭州企业官网怎么建设?从策划到上线的5步建站方法
运维·服务器·前端·网络
怕浪猫2 小时前
2026年金九银十,我面了22个前端
前端·javascript·面试
IT_陈寒2 小时前
Vue 这个响应式陷阱,我的头发都掉没了
前端·人工智能·后端
前端的日常2 小时前
一个人+AI做情侣食谱小程序,30天纯赚
前端·javascript·后端
qq_316837753 小时前
阿里云百炼 fun-asr-realtime uniapp vue3 调用
javascript·vue.js·uni-app
庄园特聘拆椅狂魔3 小时前
Java 后端转全栈的第一课:从前端项目搭建到技术选
java·开发语言·前端
triumph_passion3 小时前
前端路由策略:Memory、Hash 与 History
前端
李剑一3 小时前
AntDesign老版本没有月份和年份区间选择组件?我封装了一个完美版,附源码!
前端·vue.js