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。

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

相关推荐
Captaincc3 小时前
AI用量v0.1.11更新发布 新增 jusage doctor 诊断指令 托盘展示token 和余额 新增 AutoClaw 支持
前端·后端·vibecoding
计算机魔术师4 小时前
德国Wiki被黑后两周,OpenAI终于把模型失控的账本摊开了
前端
kyriewen5 小时前
我让 AI 当面试官面了我一轮:第 3 个追问我就卡住了(附 10 道追问清单)
前端·面试·ai编程
IT_陈寒5 小时前
Python的GIL把我坑惨了,多线程跑得比单线程还慢
前端·人工智能·后端
前端snow6 小时前
ai agent --- 多agent框架之图编排引擎-langgraph
前端
竹林8186 小时前
OmniPic Studio v3.2.1 核心技术架构与全平台发版解析文档
前端·浏览器
JamesZhang800786 小时前
页面内存只涨不跌? 一次泄漏排查, 牵出 WeakMap 的诞生
前端
Z小明6 小时前
第 6 章 组件进阶
前端·vue.js
江华森6 小时前
HTTP请求的完整过程详解:从DNS解析到TCP挥手的微秒级实战分析
前端
南青6 小时前
Vue 3 中后台实战:我踩过的 10 个坑和最佳实践
前端