不是说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轮批量更新):
| 方案 | 耗时 |
|---|---|
| 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-…
觉得有帮助的点个⭐️支持下 🙏