查看原文
其他

数据流 2022

云谦 云谦和他的朋友们 2022-10-17

这段时间把主流数据流代表的源码翻了一遍,同时看到内网同学闻冰有写一篇 6000+ 字的「谈谈复杂应用的状态管理(上):为什么是 Zustand」,手痒也想写写我对于现阶段数据流方案的理解。

1、说到数据流,大家可能会想到老牌的 Redux、Dva、Mobx、RxJS、XState,也可能会想到新一代的 Recoil、Jotai、Zustand、Valtio、React Tracked、Redux Toolkit,以及还有很多使用度没那么广的方案 use-context-selector、react-easy-state、hox、useModel in umi、icestore、kylva、overmind 等,同时很多简单场景可能会直接裸用 hooks 组织。这些方案大家应该多少都有用过一些,那他们之间有啥区别?又应该如何选择?选择时应该如何考虑呢?

2、数据流方案应该关注啥? 整理之后发现还是有不少的。比如心智模型、读取数据、写入数据、数据推导、异步 Action、渲染性能优化、Suspense 并发模式支持、SSR 支持、React 之外访问、组件封装、瞬时更新、插件中间件扩展、Redux DevTools 支持、兼容性、多实例和单实例、数据序列化能力、同步/异步更新、内存管理、测试、包尺寸等。(还有啥?)大部分数据流方案都考虑了这些点,区别是实现方式和使用体验上的差异。

3、心智模型指的是「外部 store」和「内部 store」的区别。外部 store 以数据为中心,设计应用时设计的是数据,从数据角度出发,无需考虑组件,从上到下地组织,比如 Redux、Dva、Zustand 等都属于这类;内部 store 以 React 组件为中心,设计应用时以组件为主,从下到上原子化的组织(原子是一个最小但完整的状态单位,它们是小块的状态,可以连接在一起形成新的派生状态,最终形成了一个图),比如新出的 Recoil、Jotai、React Tracked 都是此类。

4、写入数据直接影响研发体验,分「immutable」和「mutable」。比如 `state => return { ...state, count: state.count + 1 }` 是 immutable,而 `state => state.count += 1` 是 mutable,后者明显更符合编码直觉,尤其是涉及嵌套结构时,immutable 写起来会比较痛苦。从实现上看,基于 proxy 的通常是 mutable,比如 Valtio、React Tracked、MobX;其他都是 immutable。但由于 immer 的存在,很多 immutable 的方案都可借此实现 mutable 写入数据。那基于 proxy 是不是没优势了?不是的。

5、proxy 影响的除了写入数据,还有渲染优化。渲染优化分手动、半自动和全自动。手动是通过 selector 实现,比如 Redux 系、Zustand 和 use-context-selector 都属于此类,selector 非常依赖开发者素质,select 多了就可能会导致不必要的渲染;全自动的有 Valtio、react-tracked、react-easy-state,实现方案是基于 Proxy 做的使用追踪,开发者无需关心默认最优渲染性能;半自动比如 Jotai、Recoil 和 Mobx[? 待确认],需要用户手动声明依赖,实现方式是基于依赖追踪(a 依赖 b,b 更新了会导致 a 更新),有些基于 proxy 有些不是。

6、其他功能点简单说下我的理解。异步 Action 已是新一代数据流方案的基础能力,大多是 `async action(){}` 直接声明直接用,除了 Redux Toolkit 的用法还有点搓,但估计也是基于 Redux 方案优化的极限了?Suspense 大家也已逐步支持,主要是内部实现挑战,遇到 promise 也要 throw 出来,开发者使用上应该是透明的。SSR 支持 对于外部 Store 的数据流来说会稍显复杂,尤其是有多个 store 同时中间还存在共享依赖时会更麻烦,所以 Zustand 和 Valtio 都有 Context 模式以提供初始值。React 之外访问可能在 1% 的场景会用到,如果没有可能会形成卡点,据我了解应该就 jotai 不支持。组件封装是强需求,这也是单一 store 的 Redux 没落的原因之一,随着业务发展项目变多,肯定会有提取公共组件的需求,这方面 Redux 和 Dva 非常弱,而新出的外部 store 方案都是多 store,无此问题。瞬时更新 指拿最新数据但不订阅更新触发 rerender,通常基于 ref 都可实现。兼容性 的分水岭是 proxy,基于 proxy 唯一缺点就是兼容性。

7、数据流除了本地状态,还有来自远程服务器的状态需要同步。而很多 CURD 项目的 80% 需求只需要做后者,所以社区涌现出不少专门解远程状态的库,比如 React Query(已更名为 TanStack Query)、SWR、Apollo、Relay、RTK Query、use-request 等。广义地看,这些库也是数据流方案。同时不得不提的是,Remix 通过在服务端做数据加载的方式,把下图的数据流环 ui=fn(state) 扩展到了全栈。

8、所以怎么选?没有银弹!如果站在社区开发者的角度来看。先看远程状态库是否满足需求,满足的话就无需传统数据流方案了;然后如果是非常非常简单的场景用 useState + Context,复杂点的就不建议了,因为需要自行处理渲染优化,手动把 Context 拆很细或者尝试用 use-context-selector;再看心智模型,按内部 store 或外部 store 区分选择,个人已经习惯后者,前者还在观望;如果选外部 store,无兼容性要求的场景优先用类 Valtio 基于 proxy 的方案,写入数据时更符合直觉,同时拥有全自动的渲染优化,有兼容性要求时则选 Zustand。

你怎么看呢?


本文是我在「知识星球」中「日更短文」的其中一篇,计划每周四从中挑选一篇发布到公众号,了解更多可点击「查看原文」。


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存