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。

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

相关推荐
nameofworld4 分钟前
前端面试笔试(五)
开发语言·前端·javascript·面试·递归
l_tian_tian_23 分钟前
JavaWeb——Ajax、Element、打包部署
前端·javascript·ajax
moskidi24 分钟前
Web day02 Js & Vue & Ajax
前端·javascript·vue.js
星月昭铭25 分钟前
浏览器控制台中使用ajax下载文件(没有postman等情况下)
前端·chrome·ajax·postman
花下的晚风27 分钟前
vue3 发送 axios 请求时没有接受到响应数据
前端·javascript·vue.js
盏灯39 分钟前
🏆🚀🎉前端开发,实现头像从圆圈⭕️伸出来的效果🕳
前端·css
我是Superman丶1 小时前
【组件】前端ElementUi 下拉Tree树形组件 带模糊搜索自动展开高亮功能
javascript·vue.js·elementui
天农学子1 小时前
elementui el-input修改字体样式
前端·javascript·css·elementui
Sword991 小时前
【ThreeJs原理解析】第3期 | 射线检测Raycaster实现原理
前端·three.js·源码阅读
布兰妮甜2 小时前
Zustand:一个轻量级的React状态管理库
前端·react.js·zustand