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

66 阅读2分钟

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

故事是这样的

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

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

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

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

Zustand的痛点

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

// 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. 监听嵌套对象?不好意思,做不到

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

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

然后我发现了easy-model

// 用类来组织,一个领域一个类
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();

这才是面向对象!

深度监听,真香

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?

// 定义依赖
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轮批量更新):

方案耗时
Zustand0.6ms
easy-model3.1ms
MobX16.9ms
Redux51.5ms

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

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

这波不亏!

怎么选?

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

Github: github.com/ZYF93/easy-…

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