为什么选择 ZenBox?
ZenBox 与其他 React 状态管理库的对比
背景
React 生态下已经有很多状态管理解决方案了,但大多数往往:
- 使用繁琐(如 Redux 的样板代码)
- API 设计复杂,需要学习新的使用范式(如 Recoil)
- 需要额外的心智负担(如 Valtio 的 proxy 里
this
的使用) - ……
要是能有一种状态管理库,用起来既像 Zustand 一样简单,又像 Vue 一样符合直觉,就好了。
ZenBox 就是这样一个状态管理库:它结合了 Vue 和 Zustand 的优点,用起来既简单又符合直觉,非常爽!
如果你喜欢 Zustand 和 Vue,你一定会喜欢 ZenBox!
核心功能对比
自动类型推断
Zustand: 需要手动定义类型
import { create } from "zustand";
interface BearState {
bears: number;
increase: (by: number) => void;
}
const useBearStore = create<BearState>()((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
}));
✅ ZenBox: 可以从初始状态自动推导类型,无需手写 interface
const store = createStore({
count: 0,
name: "ZenBox",
increment: () => {
store.setState((state) => {
state.count++;
});
return store.value.count;
},
});
store.value.count; // number
store.value.name; // string
// 甚至可以自动推导出函数返回值类型
store.value.increment(); // () => number
跨 Store 计算值
Zustand: 不支持,需要预先组合 store 以实现共享选择器和按需渲染
interface BearSlice {
bears: number;
}
interface FishSlice {
fishes: number;
}
const createBearSlice = () => ({ bears: 0 });
const createFishSlice = () => ({ fishes: 0 });
const useBoundStore = create<BearSlice & FishSlice>()((...a) => ({
...createBearSlice(...a),
...createFishSlice(...a),
}));
const total = useBoundStore((state) => state.bears + state.fishes);
✅ ZenBox: 支持跨 Store 计算值,自动追踪依赖,按需 re-render
const bearStore = createStore({ bears: 0 });
const fishStore = createStore({ fishes: 0 });
// 像 1 + 1 = 2 一样简单
const total = useComputed(() => {
return bearStore.value.bears + fishStore.value.fishes;
});
状态访问模式
Zustand: 手动传递 get()
和 set()
参数,不支持自动类型推导
import { create } from "zustand";
const useCountStore = create((set, get) => ({
count: 0,
increment: () => {
set({
...get(),
count: get().count + 1,
});
},
}));
useCountStore.getState().count;
useCountStore.getState().increment();
Jotai: 通过参数传递 get
和 set
函数,可读性较差,增加心智负担
const count1 = atom(1);
const count2 = atom(2);
const count3 = atom(3);
const sum = atom((get) => get(count1) + get(count2) + get(count3));
// 函数式写法需要更多样板代码
const atoms = [count1, count2, count3, ...otherAtoms];
const sum = atom((get) => atoms.map(get).reduce((acc, count) => acc + count));
const decrementCountAtom = atom(
(get) => get(countAtom),
(get, set, _arg) => set(countAtom, get(countAtom) - 1)
);
Valtio: 在 Proxy 里使用 this
和 getter 需要非常小心,容易出错
const state = proxy({
count: 1,
get doubled() {
return this.count * 2;
},
user: {
name: "John",
},
greetings: {
get greetingEn() {
return "Hello " + this.user.name; // 错误 - `this` 指向 `state.greetings`
},
},
});
// 快照里的 getter 调用正常
const snap = snapshot(state);
console.log(snap.doubled); // 2
// 但状态变化不会更新快照
state.count = 10;
console.log(snap.doubled); // 仍然是 2
✅ ZenBox: 可以使用统一的 store.value
接口处理所有操作,心智负担最小
// 读取状态
const count = store.value.count;
// 更新状态
store.value = { count: count + 1 };
// 调用方法
store.value.increment();
// 计算值
const doubleCount = useComputed(() => store.value.count * 2);
Immer 集成
Zustand: 需要手动设置 Immer 中间件
import { create } from "zustand";
import { immer } from "zustand/middleware/immer";
type State = {
count: number;
};
type Actions = {
increment: (qty: number) => void;
decrement: (qty: number) => void;
};
export const useCountStore = create<State & Actions>()(
immer((set) => ({
count: 0,
increment: (qty: number) =>
set((state) => {
state.count += qty;
}),
decrement: (qty: number) =>
set((state) => {
state.count -= qty;
}),
}))
);
✅ ZenBox: 开箱即用,无需手动配置
const store = createStore({
count: 0,
increment: (qty: number) => {
store.setState((state) => {
state.count += qty;
});
},
});
按需渲染
Zustand: 需要手动使用 useShallow
进行浅比较,以减少重复渲染
import { create } from "zustand";
import { useShallow } from "zustand/react/shallow";
const useMeals = create(() => ({
papaBear: "large porridge-pot",
mamaBear: "middle-size porridge pot",
littleBear: "A little, small, wee pot",
}));
export const BearNames = () => {
const names = useMeals(useShallow((state) => Object.keys(state)));
return <div>{names.join(", ")}</div>;
};
✅ ZenBox: 默认使用浅比较(和 React 的 useMemo
一样)
const names = useComputed(() => Object.keys(bearStore.value));
需要时也可选择深比较:
const names = useComputed(() => Object.keys(bearStore.value), { deep: true });
性能提示: 大多数情况下使用浅比较即可。深比较开销更大,仅在必要时使用。
特性对比
特性 | ZenBox | Zustand |
---|---|---|
学习曲线 | ✅ 最小化(Vue 友好) | ✅ 较低 |
Vue 风格响应式 | ✅ useComputed /useWatch | ❌ 需要手动处理 |
TypeScript 支持 | ✅ 完整自动推断 | ⚠️ 需要手动接口定义 |
状态访问 | ✅ 统一的 store.value 接口 | ❌ 手动 get() /set() |
跨 Store 计算 | ✅ 自动依赖跟踪 | ⚠️ 需要预先组合 |
组件作用域 | ✅ 内置 Provider 支持状态隔离 | ❌ 默认全局状态 |
Immer 集成 | ✅ 内置支持 | ⚠️ 需要中间件 |
持久化 | ❌ 无内置支持 | ⚠️ 需要中间件 |
开发工具 | ❌ 无内置支持 | ⚠️ 需要中间件 |
打包大小 | < 3KB 压缩后(不含 Immer) | < 1KB 压缩后 |