📦 实现一个简单朴素的 react 数据管理层 NOVUS
!本篇文章过于久远,其中观点和内容可能已经不准确,请见谅!~
PS: 仓库和代码在最下面;
PS2: 后续出现了一些问题和痛点,做了很多的改进: 📦 改进一个简单朴素的 react 数据管理层 NOVUS,包括同步触发、初始化、自动依赖收集、“喘息机制”、Novus 版 Fiber 和测试,本篇重点说需求、基础功能和初步实现。
PS3: 前端项目中的数据层被造轮子无数,有很多优秀的库,大厂更不用说内部成熟的解决方案。这只是我在项目中实践的一个解决方案,仅供参考。
在现代前端开发中,组件化、模块化的引入,状态与界面的分离,导致状态数据的共享和流转部分变得很重要,稍微复杂点的项目都需要抽象出一个数据层来负责数据的管理、变更和分发。
尤其是 React、Vue 这类组件化开发框架,数据驱动的思想是基因里带的,但是自带的都是组件内部和之间的状态流转,如果需要全局的状态、跨组件的变更、多层级的状态共享,还是需要额外的状态管理,React 并没有在这个地方提供开箱即用的内置方案。
这类全局状态管理的理念能够将很复杂的应用架构拆分,在前面 🌋 WebIDE 的开发记录其二(核心架构) 文章中也说了这个事情,相比之前的状态管理分散的概念,将数据的流转拆分为全局的:触发动作、修改数据、变更通知。这样的独立的数据管理层控制的单向数据流转,保证每个节点都是接受数据更新,而不是一级一级传递,或者回调函数等,让动作、数据和界面拆分,方便更大型的架构扩展,而不用操心数据从哪来、怎么变更、怎么重绘组件的问题。
这种数据的管理模式在大型项目中非常有效,反正我目前在较大型的项目中都需要一个数据层的东西来管理数据的驱动、变更和分发。
然后索性一想,不如我也来造个轮子,一方面满足这个项目最后一块优雅的拼图,另一方面这块确实有些想法,也是时候将这些想法变成产出了。
redux 在前端开发中非常有效,而且 react、redux、react-redux 全家桶也很好用,一般的公司都会使用这个工具来管理前端的数据中心。这里也轮不到我来对这个优秀的工具评头品足的,真的非常优秀。
但是优秀的意思并不是适合每一个人和每一个项目,很多吐槽和讨论也充斥各大社区,比如比较多的:
- 概念太多
- 加上副作用和胶水中间件处理就会有 store、states、action、reducer、dispatch、payload、immutable、take、put、call、connect、mapStateToProps、mapDispatchToProps 这些概念门槛会有劝退效应,想入门必须花时间学习和实践,虽然在数据管理中只要深入抽象,这些都会涉及,但是每个项目或者新人都会有很多的学习和适应成本。
- 模式代码
- 洁癖的开发形式,每个状态的管理都要拆分到不同的文件或者逻辑中,函数式、不可变性的代价是繁琐的模式代码,状态小改一下都要改好几个文件,也因此几乎大型项目都会对这块进行封装,因为确实维护起来会很困难。好处是逻辑清晰,像是机房的数据线整理,绕了很多圈但是看起来很干净。
- 逻辑拆分
- 编程很多人更喜欢的是相似的逻辑会放到一起聚合,而不是按照流程或者功能拆分,比如学生信息直觉应该放到一个抽象中,而不是查询和更改分开。逻辑上的抽象每个团队偏好不同,这里应该有不同的实践。
当然需求的取舍是一门艺术,不可能有一个版本能满足每一个项目需求,甲之蜜糖,乙之砒霜也是很自然的事情。比如上面的不方便的这种范式,对于调试、对于数据的同步和管理、对于服务端渲染、对于测试、对于项目数据初始化等却是相对优雅的实现。
Mobx 使用装饰器的语法实现了更加清爽的功能,也是很多人推崇的一个方案,我也很感兴趣,不过我可能对装饰器不太感冒,而且也没咋用过。
当然 redux 或者 mobx 也并没有一个官方的开发模式,更多的是一种理念,每个人都可以根据这个理念实现自己的工具,当然也包括我~~
技术发展到现在更是百花齐放,很早之前这些讨论就有很多: 阿里早期的讨论:MobX 和 Redux 的比较 对 antd 框架里的吐槽:忍不住吐槽一下Flux和Redux
当时那个项目开发的时候直接用了封装比较好的 DvaJS 来做开发,很多方面已经改善了开发体验,可能自己用的是比较早期的版本,没有 TS 支持,仅考虑 DvaJS 有一些不顺手的地方:
react-router
和fetch
的内置并不需要redux
和redux-saga
很多概念特性,put
、call
、payload
、action
、dispatch
等逻辑一个没少,而且没办法推断;action type
字符串或者常量类型,没办法代码提示,没办法推断;payload
作为参数,没办法代码提示,没办法推断;- 继承自
redux-saga
的Generator
Iterator
的effects
,用到yield *
异步处理方式。不习惯、不喜欢; - 还想要一次数据结构定义关联的地方都能立刻反映出来而不是运行时连接。
因为这些原因,在后期全面转向
TypeScript
时,一直想把这块替换掉。数据层的需求一般也很简单,还是类似
dva
集中组织数据的方式,但是不使用 redux 字符串模板的形式。就是能储存、能改变、能响应基本上就可以了,甚至可以简单理解为一个发布订阅系统。最基本的需求有:Model
数据源state
按照逻辑集中数据,能定义ts type
- 不追求绝对干净的方法改变
state
,但是提供方便的习惯约定 - 不需要
devtool
管理历史,没有时间漫游的需求,也不刻意强调修改状态逻辑的干净、简单,所以不特意设置reducer
概念,避免语法啰嗦(不强调协作+重放等场景) - 不特意设置
reducer
,但是仍然可以封装setState
为纯函数的处理来实现reducer
相似的功能,这里因为对数据的流转不刻意强调,所以不预设 - 不特意强调副作用,而强调 状态本身 和 修改状态的行为,纯函数会更好,但是我希望这是可选的,而不是用很奇怪的代码组织来区分和限制。
- 改动能订阅,状态本身 + 修改状态的行为 + 状态能被订阅
- 能通过
connect
或者useHooks
方法使用和订阅数据的变化通知,从而渲染组件。 - 最大的驱动是能够用
ts
的类型限制和推断,自动提示数据的访问和action
的参数和返回。
不特意强调副作用,而强调 状态本身、修改状态的行为 和 状态能被订阅,没有副作用的纯函数会更好,但是我希望这是可选的,而不是用很奇怪的代码组织来区分和限制。
实现的时候甚至没有花太多精力,也很方便的替换掉了原来的数据管理层,现在用起来感觉清爽很多,也完全满足较大项目的使用,比如 🌋 WebIDE 的开发记录其一。
使用 novus 之后,状态的引入、动作的调用都有完整的代码提示,而且很自然的将 【数据状态】 和 【状态的改变行为】 区分,异步和同步的强调变为方法和赋值。
核心代码也就几十行,也可以看作是一个带有状态数据的订阅通知工具,主要是实现了完整的 TypeScript 支持,在具体使用的地方比较方便。
或者直接在本文看,全部代码和实例如下:
感谢您的阅读,本文由 Ubug 版权所有。如若转载,请注明出处:Ubug(https://ubug.io/blog/novus)