说说zustand的缺点——对几个用过的状态管理工具的总结

主要是与mobX的一些对比,redux和recoil很久没用了

一家之言,我是彩笔

redux

记忆里redux的不可变树特性会导致一些意料外的刷新(应改是有办法解决的才对),并且心智负担有点重,需要自己构建action等,虽然又toolkit的加入据说会好很多,但是最后还是被我放弃了,太累了

mobX

我认为他是最简单的能够对应上OOP的一种状态管理工具了,装饰器语法很棒,我没有用得很深并且有一阵子没用了,说些自己的见解

  1. 最新版还能用装饰器语法吗?如果支持也只是 怕别用用别怕,我永远忘不了它,makeObservable 一个一个加还是麻烦,而且 construct 跟实际的代码分离,最后还不如 makeAutoObservable
  2. makeAutoObservable 或许并不是最好的选择:取决于你是否将 mobx object 单纯地作为一个 store 处理

mobx 中属性是否是 state/action 应该是有选择地去处理而不是交给 auto,整个class可以根据业务封装成独立的 data manager model,根据实际的使用情况,内挂很多private属性/纯函数等与state无关,但是与业务相关的东西,和 hook 的构建类似,应该是基于业务的OOP

zustand

最近在用,并非本人选型,但是用着还行,追求极致的打包大小时这是个好选择,项目规模大时建议不要考虑

优点
  • 不像mobx还需要observer包裹组件
  • 体积小,但是其实没差多少,不是极致追求体积的场景我觉得差别不大
说说缺点
ts 复制代码
const list = useListStore(state=>state.list);
// ...
list.map
  • jsdoc 注释以及引用关系会在这里丢失,list 只会指向 const list =,需要继续查找state.list的定义
  • 他是 hook 的用法,use 开头一个 store 很怪,如果命名不以use开头, userStore(...)这样作为方法调用一个 store 也很怪,始终没有想到一个简单明了的语义设计
ts 复制代码
// 构建方式1
interface StoreType {
  /** xxx 用途的列表 */
  list: { id: string }[];
}

const useListStore = create<StoreType>((set, get) => ({
  /** 这里注释并没有什么用处 */
  list: [],
}));
  • 类型和数据分离,跟上面一样,如果要查找初始值,查找定义时,会跳到类型,然后根据类型查找,要在一堆引用里找到 store 的初始定义
  • 当属性很多(虽然不应该让他很多),类型很长时,需要将 StoreType 分文件管理,更难受了
ts 复制代码
// 构建方式2
// 如果需要自动推断类型,需要不传入 set get
const useListStore = create(() => ({
  /** xxx 用途的列表 */
  list: [] as { id: string }[],
}));
  • 构建方式2可以避免上面的问题,但是每个初始值都需要去断言,依然会存在jsdoc丢失的问题,并且无法在 store 内直接使用 set/get 而需要 useListStore.setState 这与库的原始设计相悖,这样比起来基于 class 的 mobx 在使用上要舒服很多,可以 list:Type = []
  • 官方示例中 将 action 写在 store object 内真的是很迷的一步,为了聚合一个 state 相关的逻辑,会在 store object 内写很大的一块,我在实践中更多的是基于文件块进行管理,尽可能降低 store object 本身的行数(主要还是减少全局 state 的使用),让逻辑更加清晰
ts 复制代码
import { create } from 'zustand';

export interface BaseStore {
  /** 广告 - 打开推荐创作者 */
  excellentShown: boolean;
  /** 打开编辑精选 - 当前展示期数
   * - `''` 为默认期数
   * - `undefined` 为关闭编辑精选
   */
  editPickPeriod?: number | string;
  /** 排行榜(如果分成两个状态处理会导致 effect hook 执行两次)
   * - `undefined` 为关闭排行榜
   */
  rankingSource?: {
    type: 'fc_day' | 'fc_week' | 'active_day' | 'active_week';
    /** 排行榜期数 */
    period?: string | number;
  };
  /** 是否显示提现页 */
  payoutPageShown: boolean;
  /** feed 流 socket 触发的数量变化未加载的数量 */
  newFeedCount: number;
  /** null 表示显式的未获取到,undefined 表示 prefetch 可以再等等 */
  customLink?: string | null;
}

const baseStore = create<BaseStore>((set, get) => ({
  excellentShown: false,
  editPickPeriod: undefined,
  rankingSource: undefined,
  payoutPageShown: false,
  newFeedCount: 0,
}));

export default baseStore;

const get = baseStore.getState;
const set = baseStore.setState;

export const baseStoreUtils = {
  /** getter - 右下角固定按钮显隐 */
  getFixedToolsHidden: () => {
    const { payoutPageShown } = get();
    return !!payoutPageShown;
  },
  /** 重置设置会导致右下角固定按钮显隐变化的状态 */
  resetFixedToolsHiddenState: () => set({ payoutPageShown: false }),
  setPayoutPageShown: (shown: boolean) => set({ payoutPageShown: shown }),
};
  • 基于AOP(面向切片)的概念,无论是 action 还是 getter 都是很有必要的,前几天查一个数据变更行为导致的bug时,面对💩山里遍地的 xxxStore.setState({xxx:xxx}) 我是十万分的崩溃,zustand 在设计上非常的灵活,这在编码上是很方便,但在项目管理上真的很让人头疼,无法制造 readonly 的 getter 导致一堆对项目没有敬畏的代码出现,这不是好事

结论

无论选型什么状态管理工具,其实差别都没有那么大,至少在人与人的差距面前是这样的,遇到堆💩人,用什么都救不了,面对项目管理的工作,新的不一定是好的,coooooool 的代码需要 cool 的队友

相关推荐
豆苗学前端6 分钟前
vue3+TypeScript 实现一个图片占位符生成器
前端·面试·github
neon12048 分钟前
Vue 3 父子组件通信核心机制详解:defineProps、defineEmits 与 defineExpose 完全指南
前端·javascript·vue.js·前端框架
Juchecar25 分钟前
Vue3 开发环境搭建及循序渐进学习指南
前端·javascript
Data_Adventure41 分钟前
@scqilin/phone-ui手机外观组件库
前端
一点一木1 小时前
Vue Vapor 事件机制深潜:从设计动机到源码解析
前端·vue.js·vapor
FSHOW1 小时前
记一次开源_大量SVG的高性能渲染
前端·react.js
小牛.7931 小时前
Web第二次作业
前端·javascript·css
二闹1 小时前
都2025了还要用Layui做下拉控件-我只能说你有水平
前端
Pikachu8031 小时前
揭秘 tyarn:一个为大型 TypeScript Monorepo 优化的 Yarn 性能猛兽
前端·javascript
用户49430538293801 小时前
大规模建筑自动贴图+单体化效果,cesium脚本
前端·javascript·算法