数据流是是 UI 库的核心,现有的御三家几乎都是数据驱动界面,但是因为实现方式和理念的不同,数据流方案也略有区别。尤其是 react 的数据流方案,轮子年年有,那么今年应该用什么数据流方案呢?

React 原生的数据流

react 的核心就是 UI =X(data),数据流的重要性不言而喻,react 方法也提供了自己的数据流方案 props,props 严格的遵循单向数据流,并且只能从父传子,数据流向异常清晰,也几乎没有任何心智负担。

image.png

但同时也造成了一个问题, 兄弟组件之间的数据共享非常繁琐,只能通过共同的父组件来实现。但是这种方案看似跨组件,实则还是逐级传递,写起来异常繁琐。

react V16.3 以后提供了新的 context 来解决问题,但仍然是将组件的部分状态控制权交给了父组件的操纵,一旦父组件的值变化,子组件都会跟着更新。尤其 react 中 顶层的组件的更新一定会触发子组件的re-render,虽然我们可以通过 ShouldComponentUpdate 或者 memo 来进行一些优化,但是项目大了之后我们可能并没有很多精力来维护。
image.png

但是对于业务来说,我们有时候可能需要持久化状态,或者需要一些中间件比如操作日志。react 的自带的数据流就没法满足需求了,还有性能的优化,我们当然希望写出默认就性能优秀的代码。
1790977280-5c467320bed7e_articlex.gif

Redux 和 DVA

这时候 redux 就闪亮登场了,现在提起 redux ,可能只会想起繁琐的样板文件和拉胯的 typescript 支持。

image.png

但是 redux 实现了很多强大的功能

  • 统一的数据仓库让 debug 变得非常容易,配置 chrome 插件,感觉就像开了上帝视角
  • 可回溯,可预测,持久化都有天然的优势
  • 性能更好,而且无需做专门的关注
  • 让中间件添加更容易,几乎没有成本
  • 纯函数,几乎没有副作用(脏代码都给了插件了)

306326921-5c348b01dd83f_articlex.gif
但是对于大部分项目来说它带来的优势远不如他带来的问题多,单项的数据流有点反直觉,我明明在组件中 dispatch,却要从 props 中拿 loading,甚至数据什么时候返回也要从 props 中获得。多余提交之后去做 xxx 的代码来说写起来非常变扭。甚至很多会选择使用回调来破坏单项数据流。

image.png

Mobx

redux 的繁琐和反直觉让很多很难受,所以有一部分人就转投了 mobx,mobx 基于 proxy 特性来实现数据的追踪,相当于自动帮你写 redux 中的 action。

Observerview 会自动做出响应,这就是 mobx 主打的响应式设计,但是编程风格依然是传统的面向对象的 OO 范式。(熟悉Vue的人一定对这种响应式设计不陌生,Vue就是利用了数据劫持来实现双向绑定,react+mobx 充满了 vue 的既视感 )

image.png

mobx 把最简单的交给了用户,剩下的复杂的交给自己去处理,用户用起来非常方便。而且通过数据劫持来实现了真正的局部更新,性能在很多时候都是不错的。

但是事情往往不会很完美,mobx 也有自己的问题,最麻烦的就是副作用,很多时候你不知道到底是谁更新了数据,也无法做数据更新。而且他的 stroe 非常多,出来 bug 时 debug 会让感觉到非常痛苦, 同样的他也对异步数据流支持的不太好。

image.png

rxjs

rxjs 是一个非常强大的工具(api 120+),它践行了响应式编程的方案, 一切基于流,而我们做的只要操作流。

image.png
其订阅者模式也异常强大,可以配置 redux 甚至任意框架使用,纯函数的特性让他没有任何副作用,强大的操作符让其可以应对任何数据,特别是针对各种复杂的异步数据流,甚至可以多种事件流组合搭配,汇总到一起处理。

同样的其陡峭的学习曲线和一切基于流的思想让其使用起来非常困难,要驾驭它真的很难。甚至白白增加了项目复杂度。

image.png

unstated 和 hooks

unstated 是我个人最喜欢的数据流方案,小巧且符合直觉。它通过将 setState 共享的方式来进行数据流操作,特别像 hooks 的思路。

  1. function useCounter() {
  2. let [count, setCount] = useState(0);
  3. let decrement = () => setCount(count - 1);
  4. let increment = () => setCount(count + 1);
  5. return { count, decrement, increment };
  6. }
  7. function CounterDisplay() {
  8. let counter = useCounter();
  9. return (
  10. <div>
  11. <button onClick={counter.decrement}>-</button>
  12. <p>You clicked {counter.count} times</p>
  13. <button onClick={counter.increment}>+</button>
  14. </div>
  15. );
  16. }

unstated-next 使用了 hooks 的能力更是进一步强化了其作为全局数据的能力,没有副作用,不需要中间件。就是非常符合只觉得 useState。

hoosk 提供了共享行为的方式,redux 的核心竞争力之一的 获取数据的 逻辑封装 和 获取数据的状态封装,用 hooks 完全可以替代,并且更加优雅。不需要 dispath,不需要定义 action。

  1. function Profile() {
  2. const { data, error } = useSWR("/api/user");
  3. }

如果配合 Suspense 能力,甚至 loading 都不需要关心。获取数据能力 和 获取数据的状态 都封装到了 useSWR 中,全局需要的数据可以用 unstated-next 中获取,我们其实不需要在制造新的数据流方案了。

image.png

总结

在 hooks 来临之后数据流方案已经越来越没有用武之地,redux 现在的核心竞争力还是已经只剩 中间件 和 可回溯 了,一般业务中却很少需要这个功能。mobx 更是几乎没有了优点。

image.png
这里做了一个雷达图,hooks 几乎是六边形战士,在状态共享,异步支持和 Typescript 支持上都非常完美。
以下是我个人的一些建议。

  • 组件库使用 react 的自带的数据流,props 和 context,使用方便副作用小。
  • 数据展示的中后台应用使用 hooks,bigfish 提供了内置的方案。轻量但是强大。
  • 异步数据流比较多的使用 rx.js,比如处理各种网络请求和事件。
  • 想要体验 Observerview 的感觉,使用 mobx