我用 Zustand 三年了,直到遇见 easy-model...

不是说Zustand不好,而是有些场景它真的hold不住。

故事是这样的

我们公司有个中后台项目,状态管理一直用Zustand。讲真,Zustand确实香------API简洁、性能好、类型推断也还行。

直到有一天,产品经理提了一个需求:

"做一个操作日志中心,用户每做一个操作就记录下来,支持撤销重做。而且要能在列表页看到嵌套对象的变化轨迹。"

我自信满满地开始写,然后就被打脸了。

Zustand的痛点

1. 状态一多就成了"函数大杂烩"

tsx 复制代码
// store.ts
const useStore = create((set, get) => ({
  user: null,
  orders: [],
  filters: {},
  pagination: { page: 1, size: 10 },
  loading: false,

  setUser: (user) => set({ user }),
  setOrders: (orders) => set({ orders }),
  setFilters: (filters) => set({ filters }),
  setPagination: (pagination) => set({ pagination }),
  setLoading: (loading) => set({ loading }),

  fetchOrders: async () => {
    const { filters, pagination } = get();
    set({ loading: true });
    const res = await api.getOrders(filters, pagination);
    set({ orders: res.data, loading: false });
  },

  // ... 200行后
}));

一个文件写了500行,到后面自己都不想看了。

2. 撤销重做?自己实现吧

Zustand没有内置history支持。网上倒是有zundo这种中间件,但:

  • 配置繁琐
  • 类型推断经常出问题
  • 和业务代码集成麻烦

3. 监听嵌套对象?不好意思,做不到

tsx 复制代码
const orders = useStore((s) => s.orders);
// 改变了 orders[0].items[0].price
// 组件不会更新!因为引用没变

你得用subscribe或者自己写selector,关键是一旦嵌套深了,selector写得怀疑人生。

然后我发现了easy-model

tsx 复制代码
// 用类来组织,一个领域一个类
class OrderModel {
  orders: Order[] = [];
  filters: FilterParams = {};
  pagination = { page: 1, size: 10 };
  loading = false;

  async fetchOrders() {
    this.loading = true;
    const res = await api.getOrders(this.filters, this.pagination);
    this.orders = res.data;
    this.loading = false;
  }

  setFilter(key: string, value: any) {
    this.filters[key] = value;
  }
}

// 内置history支持
const order = useModel(OrderModel, []);
const history = useModelHistory(order);

// 撤销重做,一行搞定
history.back();
history.forward();
history.reset();

这才是面向对象!

深度监听,真香

tsx 复制代码
class ComplexModel {
  user = {
    profile: {
      address: { city: "北京" },
    },
  };
  orders = [];
}

// 监听嵌套对象变化
watch(user, (keys, prev, next) => {
  // keys: ['profile', 'address', 'city']
  console.log("变化了", keys, prev, next);
});

user.profile.address.city = "上海";
// 自动触发监听,拿到完整的变化路径

还有IoC?

tsx 复制代码
// 定义依赖
const apiSchema = object({
  baseUrl: string(),
}).describe("API配置");

// 注入
class OrderApi {
  @inject(apiSchema)
  config?: { baseUrl: string };

  async getOrders() {
    return fetch(`${this.config?.baseUrl}/orders`);
  }
}

// 配置
config(
  <Container>
    <CInjection
      schema={apiSchema}
      ctor={OrderApi}
      params={["https://api.example.com"]}
    />
  </Container>,
);

这不妥妥的企业级架构?

性能对比

官方benchmark(10万个元素,5轮批量更新):

方案 耗时
Zustand 0.6ms
easy-model 3.1ms
MobX 16.9ms
Redux 51.5ms

easy-model比Zustand慢3倍,但换来了:

  • 类模型组织方式
  • 内置IoC能力
  • 深度监听
  • History支持

这波不亏!

怎么选?

  • 小项目、简单状态 → Zustand依旧真香
  • 中大型、需要领域模型、需要IoC、需要history → easy-model真香

Github: github.com/ZYF93/easy-...

觉得有帮助的点个⭐️支持下 🙏

相关推荐
小码哥_常3 小时前
一套自己部署的 IM,客户端是同一份 Flutter
前端·后端
蜗牛攻城狮5 小时前
微前端四大方案选型对比与最佳实践:qiankun、micro-app、wujie、Module Federation
前端·微前端·qiankun·前端架构·micro-app·wujie
她说可以呀7 小时前
Vue插槽
前端·javascript·vue.js
qq_448011167 小时前
Vue + Jinja2两套模版与法冲突造成undefinedError
前端·javascript·vue.js
aixingpan7 小时前
aixingpan.cn API开发文档:前端查询夏令时和时区接口指南
前端·php
2601_958352908 小时前
23.5×19mm 换掉一整条音频链?|A-68 集成了什么:ADC / DAC / 24.576MHz 时钟 / AEC / 波束降噪,主控仅需 6 根线
前端·音视频·ai降噪·回声消除·语音模块
志尊宝8 小时前
AI原生前端开发(039):AI辅助的Nuxt 4全栈开发
前端·vue.js·ai-native
FRing039 小时前
JavaScript入门到精通三十二(深拷贝、浅拷贝、递归函数)
开发语言·前端·javascript
excel9 小时前
双 Token 与前端使用 Crypto 公私钥鉴权换 Token,哪种方案更好?
前端·后端
外收内放9 小时前
Python基础语法练习题(73)
java·前端·python