Skip to main content

Command Palette

Search for a command to run...

分享一下我的zustand错误使用案例

Updated
2 min readView as Markdown
分享一下我的zustand错误使用案例

什么是 zustand?

zustand 是一个状态管理库,简单易用。

在使用 react-scan 的时候,我发现我在 A 组件中对于 store 的某个 state 的更新,导致了 B 组件的重新渲染,有比较严重的性能问题。

于是我又重新阅读了文档,发现我之前对于 zustand 的使用是错误的。

错误用法 1 没有使用 single selector 或者 shallow 去获取 state

这是之前的写法以及对应的组件,useGlobalStore 中有不只 uploadToServerProgress, uploadStatus 两个 state。

const ComponentA = () => {
  const { uploadToServerProgress, uploadStatus } = useGlobalStore()
  return (
    <UploadStateContainer
      uploadToServerProgress={uploadToServerProgress}
      uploadStatus={uploadStatus}
    />
  )
}

当 uploadToServerProgress 或者 uploadStatus 更新的时候,UploadStateContainer 会重新渲染。(符合预期)

当 useGlobalStore 中的其他 state 更新的时候,UploadStateContainer 也会重新渲染。(不符合预期)

这是因为 const { uploadToServerProgress, uploadStatus } = useGlobalStore();

其实是解构赋值的简写,等价于

const globalStore = useGlobalStore()
const { uploadToServerProgress, uploadStatus } = globalStore

对于 react 来说,只要 globalStore 更新,就会导致 UploadStateContainer 重新渲染。因此我们需要改为以下写法:

写法 1 single selector

const uploadToServerProgress = useGlobalStore(state => state.uploadToServerProgress)
const uploadStatus = useGlobalStore(state => state.uploadStatus)

写法 2 使用 useShallow

const { uploadToServerProgress, uploadStatus } = useGlobalStore(useShallow(state => ({
  uploadToServerProgress: state.uploadToServerProgress,
  uploadStatus: state.uploadStatus,
})))

错误用法 2 使用 selector 的时候颗粒度不够

cameraData 是通过订阅其他的服务获取的,内部包含包括相机图像、相机 id 等数据。其中相机图像是高频更新的数据。

import React from 'react';

export interface CameraData {
  cameraBase64: string; // base64 encoded image
  cameraId: number;
}

const CurrentCamera: React.FC = () => {
  const cameraData = useGlobalStore(state => state.cameraData);

  return <CurrentCameraId cameraId={cameraData.cameraId} />;
};

如果这样写,虽然这里也是 single selector,但是当 cameraBase64 更新的时候,cameraData 会更新,CurrentCameraId 也会重新渲染。

因此这里还是遵循最小粒度原则,只获取需要的数据。

import React from "react"

export interface CameraData {
  cameraBase64: string // base64 encoded image
  cameraId: number
}

const CurrentCamera: React.FC = () => {
  const cameraId = useGlobalStore((state) => state.cameraData.cameraId)

  return <CurrentCameraId cameraId={cameraId} />
}

错误用法 3 使用了多个 store 去存储不同的 state,而不是使用 slice 把不同的 state 分组

在官方的文档中 Flux inspired practice - Zustand 提到,应该使用单一存储,而不是使用多个 store 去存储不同的 state。

[!quote] Recommended patterns 推荐模式

Single store 单一存储

Your applications global state should be located in a single Zustand store.

你的应用程序的全局状态应该位于一个单一的 Zustand 存储中。

If you have a large application, Zustand supports splitting the store into slices.

如果你有一个大型应用程序,Zustand 支持将存储拆分为切片。

错误用法 4 与 UI 无关的 state 通过 selector 获取

在一个组件中,我有一个 handleSave 的方法,调用接口保存数据。

我之前的写法是,在组件 A 内部,使用 single selector 获取需要的数据,然后调用接口保存数据。

但是这样的写法是错误的。

const ComponentA = () => {
  const { state1, state2 } = useGlobalStore(
    useShallow((state) => ({
      state1: state.state1,
      state2: state.state2,
    }))
  )

  const handleSave = () => {
    save(state1, state2)
  }

  return <SaveButton onClick={handleSave} />
}

在组件 B 中,我通过操作改变了 state1,但是由于我在 A 中获取了 state1,state1 的变化会导致 A 组件重新渲染。

正确的做法是,直接在 handleSave 方法内部访问 store,而不是通过 selector 将状态传递给它。

const ComponentA = () => {
  const handleSave = () => {
    const { state1, state2 } = useGlobalStore.getState()
    save(state1, state2)
  }

  return <SaveButton onClick={handleSave} />
}

参考

Zustand

master 分支下的 notes/zustand 学习笔记.md --- notes/zustand学习笔记.md at master · puxiao/notes

如何有效率地管理 React 局部狀態? 這次我選擇了 Zustand! | Bosh 的技術探索筆記

More from this blog

使用google sheet + api定时获取油管播放量

起因是想要记录一下 milklove 的二搭剧 Whale Store xoxo 在油管上的预告片的播放量的 คุณวาฬร้านชำ (Whale Store xoxo) | GMMTV 2025 - YouTube 于是先搜了一下有没有可以直接使用的开源项目或者接口,然后在 批量统计YouTube视频播放量方法 - 杨哥的出海营销笔记 这篇文章中了解到了可以使用 google sheet 的 apps 脚本 直接获取油管的播放量。 不过这篇文章中的脚本更多的获取当前的播放量,而不是定时获取,...

Dec 15, 20242 min read16
使用google sheet + api定时获取油管播放量

raycast + imessage 2fa,快速获取验证码

很久之前做过一个 messauto 的安利 MessAuto + iMessage 实现iPhone和mac信息同步和自动复制验证码 但是最近收到评论才发现作者在前段时间删库了 目前找到另一个快速获取验证码的方法,就是 raycast + imessage 2fa 插件。 使用方法: 在获取到验证码之后,访问 imessage 2fa 插件。 可以看到目前接收到的含有验证码的消息,敲回车复制验证码到粘贴板。 缺点:没有 messauto 方便,多了访问 imessage 2fa 插...

Dec 11, 20241 min read2

声控烤箱-memos

12 posts

前端,设计,音乐,生活,杂谈。 之前的博客:https://blog.kazoottt.top/