Redux 状态管理框架演进之路

avatar
猫小娱 @猫眼娱乐

在 React 应用中,状态管理是一个无法绕开的话题,对于开发人员来说,如何优雅地管理状态是一个比想象中要复杂的事情,是一门真正的开发艺术。

为什么需要状态管理

众所周知,React 应用是由一个个状态机组合而成, 组件是 React 应用的核心,状态则是一个或者多个组件的灵魂。对于一个成型的 React 应用来说,组件构成了应用的骨架,而状态则是贯通全身的血液是应用充满活力和生机的关键。

随着应用的复杂化,要准确区分哪些组件需要状态将是一件无比困难的事情。如果将所有状态都存放在根组件,通过 context api 来消费状态未免不是一种简单的方法。但这并不能有效地解决问题,因为我们不仅需要通过 setState 细粒度控制状态变化,还要设计各组件间的状态通信。一旦设计不好,状态的管理可能会变得混乱,状态的变化也会因为无迹可查而变得不可预测。

外部状态管理 Redux

Redux 作为社区最流行的状态管理方案,为我们提供了可预测的状态容器。在 Redux 中,应用的状态被储存在一个 js 对象中作为单一数据来源,可以随时被获取。将我们的注意力集中在这个对象上,排除了其他环节的开发和维护成本。并且应用的状态是“只读”的,不能直接修改状态。修改状态的唯一方法是派发(dispatch)相应动作(action)交由相应的纯函数(reducer)来更新状态,然后返回一个全新的状态对象交由页面渲染。

通过上述约定,在 Redux 中,每一个状态的更改都是有迹可循,有据可查,这对我们状态管理的编程体验和代码维护以及后期 bug 排查极其重要。Redux 本身作为一个订阅发布系统,具体的内部处理流程如下:

redux
Redux 是最优的吗?

Redux 确实将状态管理简单化,合理化,但作为一个精简的库,它却有着陡峭的学习曲线,令人望而生畏的函数式编程概念和令人一头雾水的概念,让众多开发者望而却步。而且在实际的开发中,Redux 的开发和维护也是一件很繁琐麻烦的事情。

Rematch 的作者 Shawn McKay 曾在 《Redesigning Redux》 中对 Redux 提出了 6 个可改进之处,但具体可概括为以下 3 点:

  • 函数式编程概念繁多
  • 样板文件难管理(需要在 reducer, saga, action 之间来回切换)
  • 异步处理问题

上述问题看似平常,但与我们平时的状态管理息息相关。因为这些问题,Redux 不但不会提高我们的工作效率,甚至会降低工作效率。但 Redux 的状态管理思想是正确的,实际的项目也需要这种思想。因此为了更好地使用 Redux,降低其学习和使用成本,我们急切需要基于 Redux 的最佳实践。

最佳实践 Dva

随着 Redux 使用程度的加深,其暴露出的工程化问题就愈加严重。社区内存在不少解决 Redux 工程化问题的方案,其中 Dva 可谓是最亮眼的那颗星,被业界称为是基于 Redux 的最佳实践。

Dva 是基于 Redux 现有应用架构(redux + react-router + redux-saga 等)的一层轻量封装,帮我们自动化处理了 Redux 架构中繁琐的步骤,比如样板代码维护、store 的创建和中间件的配置,将原来那些恶心、繁琐、容易出错的步骤掩藏起来,用简洁明了的 api 来进行配置和控制。

dva

函数式编程概念繁多

这部分问题主要集中 Redux 的初始化上,Dva 通过基于对象配置的方式解决初始化函数组合的问题,具体如图:

Dva 初始化

样板文件

在样板文件上,Dva 只解决了 reducer 和 redux-saga 样板文件的自动化管理,并没有解决 action 的样板文件管理,因此这也算 Dva 的一个缺点吧。

model 是 Dva 的重要概念,对 Redux 社区有着不小的影响,它将 Redux 下的每一个子状态的下的 state、reducer 和 sagas 都写在一起,通过对象配置的方式使 Redux 样板文件的管理思路清晰简单。下面是一个简单的例子:

redux

Dva 将 action 分为同步 action 和 异步action,同步 action 的处理方法定义在 reducers 属性中,是唯一可以更新状态的地方。异步 action 的处理方法定义在 effects 属性中,同时也可以派发同步 action。

在注册 model 时,Dva 会按照 reducers 中定义的方法创建 reducer,按照 effects 中定义的方法创建 saga 副作用。

异步处理问题

在异步动作的处理问题上,Dva 选择了 redux-saga。Redux-saga 可以更容易管理副作用,执行更高效,测试更简单,在处理故障时更容易。通过 generator 来定义处理异步方法,让异步的流程更易于读取和写入,并且可以很容易地测试异步流程并保持 action 是纯洁性。

插件机制

在解决 Redux 工程化问题的同时,Dva 提供一套灵活的插件机制,让开发者可以深入 redux 数据流处理的每一部分,进一步提高代码的灵活性和工作效率。

Dva 是完美的吗?

即使被奉为最佳实践,Dva 依旧也因为客观因素和局限性存在以下缺点:

  • 个人维护的项目,从 contributors 上看,几乎全是作者一人贡献,对于当下流行技术没有及时做出相应的反应(例如 react hook),并且已经长时间没有进行新功能的更新
  • Dva 不是零 boilerplate(样板文件)的 Redux 最佳实践,仍然有 actionTypesactionCreators 相关维护

后起之秀 rematch

Dva 之后,社区出现了许多基于 Redux 封装的状态管理框架,但大多存在不小的局限性,甚至倒退,直到 rematch 的出现让人眼前一亮。rematch 是一个受 Dva 启发,零 boilerplate 的 Redux 实践,风格与 vuex 相似。

rematch 同样采取配置化 + model 的方案,将 redux 的实践,封装的更加简单。在设计上,rematch 与 Dva 还是有些不同的,我们来看一个完整的例子:

rematch

rematch 与 Dva 的不同在于,rematch 只是一个状态管理框架,只负责状态管理,其他一概不管,没有内置过多的库。并且创建应用步骤更少,减少了无谓的过程式调用,能通过配置绝不调用函数。

在整体使用风格上,rematch 更加轻便,将 redcuer、saga 和 action 统一使用 model 维护,redcuers 的定义更是合二为一,在定义 reducer 的各 action 处理方法时,便将对应的 actionCreatoractionType 定义下来,同时可以通过对象属性访问直接派发 action,减少了样板代码,极大程度上降低我们的代码成本。

在异步问题上,rematch 使用 async/await 替代了redux-sagathunk 方案,降低了异步动作处理的理解和使用成本。

在插件生态上,rematch 拥有相对较强的插件生态概念,将常用的 reselectpersistimmer 等都集成为了插件。在数据缓存,性能优化,开发体验优化都有进一步施展的空间,毕竟拥抱插件生态是一个良好的发展方向。

更棒的框架 唐刀

从 Redux 到 Dva 再到 rematch,我们可以看到 Redux 开发从刀耕火种到自动化的进步,状态管理变得简单高效,但总归来说,最佳实践方案基本上都是对 Redux 进行封装,在提供更简单的 API 的同时又不失任何可配置性的特点。

架构

在看到社区的进步时,同时我们也感受到一丝无奈。无奈何处?那就是看着这么香,这么高效的工具,想从已有状态管理框架迁移至新的管理框架却存在不小的鸿沟。比如我们想从 Redux 现有应用架构(redux + react-router + redux-saga )切换至 rematch,则必须舍弃 redux-saga,改变我们原有的异步处理方案,存在不小的迁移成本。 因此造成了目前新项目自动化,老项目累死累活的局面。这不是我们想看到,也是不愿意看到的情况。

rematch 启发于 Dva,可谓已经将 Redux 封装到极致,但这却只是对 Redux 工程化问题的解决,而在实际的状态管理的业务代码上依旧有着客观的优化空间,比如说:批量创建 action 处理方法。

唐刀

为了使尽可能多的项目(无论新老项目)都能用上与 rematch 一样的的状态管理框架,最大可能地提升研发效率,降低研发代价,我们决定重新打造一个基于 Redux 的状态管理框架。

我们猫眼前端团队对社区已有方案并结合实际情况进行了长时间地调研和分析,决定保留 Redux 现有的应用架构(redux + react-router + redux-saga ),达成较低的学习和上手成本,可以与原生 redux 数据流管理并存,Dva 状态管理框架无缝迁移,rematch 快速迁移的目标。并且将 react hook 纳入后续规划,准备打造基于 react hook 版的状态管理周边生态,进一步提高状态管理能力。

经过长时间开发与多次优化,我们终于打造出一款符合上述要求,并在业务代码有一定优化的状态管理框架—— 唐刀。

唐刀是一款汲取 Dva 和 rematch 精髓的数据流管理工具,做到了零 boilerplate,插件生态丰富,可以与原生 Redux 数据流管理方式共存,Dva 项目无缝迁移,rematch 项目快速迁移,并在业务代码上作出了可观的优化,极大地提高了状态管理效率。我们来看下,唐刀完整例子:

rematch

为了达到 Dva 项目无缝迁移,唐刀在设计理念和 api 设计上基本与 Dva 保持一致,平移支持了 Dva 的核心功能,并针对 dva 的不足进行了修正与补充,降低 dva 用户的学习和上手成本。在样板管理和 action 派发上借鉴了 rematch,为 Dva 项目和 rematch 项目快速迁移至唐刀提供了可能和保证。

Generator 与 async/await 的抉择

在框架的高度封装下,对于用户来说 Generatorasync/await 只是语法糖上的区别,写法不同但功能一致,sagas 副作用的创建由唐刀内部实现,开发者对此是无感知的。因此,对于 rematch 用户,这部分只是在写法上的不同,不需要做过多的改动,除此之外没有其他的不同。

初始化

为了可以与 Redux 数据流管理方式并存,在初始化时,唐刀提供了相关属性用以配置 redux 架构中的各种工具。这样便可以达成先接入,后改造的局面。具体如下:

tangdao

通过属性配置,你完全不用去关心和理解这些属性的实际调用过程,保证了快速接入项目的可能性。

插件

唐刀也是积极拥抱插件生态,在未来我们还准备打造基于 react hook 丰富数据流管理的周边生态,目前唐刀内置并提供了以下插件:

  • loading 插件,可以自定义管理异步请求状态和延时异步请求状态
  • effectThunk 插件可以在派发异步动作时返回 promise ,可以让你一步拿到异步请求结果
  • nextTick 插件可以在下次 DOM 更新循环结束之后执行延迟回调。在派发 action 之后立即使用这个方法,可以获取更新后的 DOM以及最新 store.state

在状态管理的相关业务代码上,针对写法相同,只是更新属性不同的 action 处理方法,唐刀提供了批量创造 action 处理方法的属性,让你的 model 代码变薄,少写更多的代码。具体如下:

updateState

从上述对比可以看出,唐刀不仅要把状态管理简单化,高效化,还要尽可能兼顾到已有方案的迁移,让尽可能多的项目拥抱简单高效的状态管理。在这一过程中,我们没有贬低任何方案,尽可能做到取百家之长,以更低的学习成本,更少的样板代码和更少的学习成本,来拥抱 Redux 背后的简单哲学。

如果你还在备受 redux 架构或者其他状态管理工具的折磨,那么不防尝试一下唐刀,说不定它会给你带来意想不到的收获,心动不如行动,早用早解脱。

最后的最后,小编卖身求 star,小哥哥小姐姐们给我点它!
Github:github.com/MaoYanTech/…