主要是与mobX的一些对比,redux和recoil很久没用了
一家之言,我是彩笔
redux
记忆里redux的不可变树特性会导致一些意料外的刷新(应改是有办法解决的才对),并且心智负担有点重,需要自己构建action等,虽然又toolkit的加入据说会好很多,但是最后还是被我放弃了,太累了
mobX
我认为他是最简单的能够对应上OOP的一种状态管理工具了,装饰器语法很棒,我没有用得很深并且有一阵子没用了,说些自己的见解
- 最新版还能用装饰器语法吗?如果支持也只是 怕别用用别怕,我永远忘不了它,makeObservable 一个一个加还是麻烦,而且 construct 跟实际的代码分离,最后还不如 makeAutoObservable
- 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 的队友