由头
自 2019 年年中以来,由于 React Hooks 的出现,以及项目 Redux 技术债过重,公司项目全面转为用 useContext
做状态管理。至今已一年半,我们再没碰过 Redux,项目开发似乎并未因此受到任何影响。
「Redux 还有必要用吗?」我时不时会想到这个问题。相比 useContext
,Redux 的 action
、actionCreator
、reducer
三板斧实在太过繁琐笨重。Redux 到底提供了什么价值,这么繁琐到底有什么帮助?没想清楚这个问题,每一板斧都像是在劝退。
有幸最近读到 Mobx 作者的一篇文章 UI As An Afterthought,寥寥数语便解开了我的困惑——Redux 即便是在当前 hooks 遍地的环境中,也是有存在的意义的。
这篇文章大意是说 Redux/Mobx 之流存在的意义,是将数据(或曰业务逻辑)抽象成单独一层,与 UI 独立开来。数据不依赖 UI:不论是用 Web、iOS 甚至是 CLI,只要接入数据层都应该能正常运行。UI 只作为展示数据的工具,也就是所谓 Afterthought(后面再考虑)
独立性
这就提现了 Redux 的价值之一:它作为单独的第三方库,有自己的一层逻辑抽象和 API,能够创建出独立于 UI 之外的数据层,而 useContext
始终是嵌在 React.Component
内,与 UI 有一定耦合的。诚然 Redux 由于「Re」开头,很容易让人将其与 React 捆绑起来(正如 Vue 和 Vite 捆绑),其实它稍加改动也能与 Vue 等适配。
可控性
独立的数据层,自然会需要专门针对数据跑测试。这时 Redux 基于 reducer
的整套设计就提现出它的优越性了。Redux 三板斧中 action
、actionCreator
都只是为了工程化而加入的模板代码,其核心其实只有 reducer
,说得更简单点,就是「根据不同 actionType
,处理 actionValue
」。
这有什么优越性?优越性在于通过有限的 actionType
,限制了 dispatch
能操作的范围,让一切可能的行为都能被预知,从而便于调试。如果是用 useContext
,把一个 setValue
的 hook 往下传,当组件层级足够复杂时,你都不知道到底是哪个组件调用了 hook、把 value
改成了什么值。
这里正好回归到一道题目:为什么要用 Redux-Saga 这类东西控制异步,而不直接写成 .then(() => dispatch())
?原因就是,让异步行为可以在 Redux 里面处理,从而可控、易于测试。
说到这儿,你可能会想到:我把 useContext
和 useReducer
组合起来用,是不是就能代替 Redux 了呢?从最终实现的功能逻辑上来说,确实做到了代替;但 Redux 的周边生态是代替不了的。
哲学性
最后提一嘴 React。React 的设计哲学是 data -> UI,即界面应该是一个仅仅基于数据的纯函数,接收什么数据,就展示什么 UI。为此 UI 中不能有任何内部状态;为此一切 data 都应从 props 传入;由于组件树每一层的每个节点都从外部读取 props,为此应当有一个中心管理所有 props,而非由组件从父到子依次传递。
基于上面的推论,我们需要 Redux 这类工具作为整个网页程序的数据中心。每个组件内部不能有任何状态,无论 props 还是 state,都应当从 Redux store 里读取,这样才能做到只要提供当时的数据,就能 100% 还原到当时的状态。这就是 data -> UI 的完美演绎。
这就是我理解的 Redux 价值。useContext
能否取代这些老牌状态管理工具的位置?未必。
增补于 2021-07-12:
2021 年 7 月 5 日,Dan Abramov 在一场分享会上提到了他对于 Redux 及 props、state 的看法,和我的理解有一定出入。莫名有种受背叛的感觉。