作者|李骏(涅尘)
来源|尔达Erda公众号

引言

大家好,这里是 Erda 开源项目前端技术团队,今天聊一聊前端的状态管理。

说到状态管理库,想必前端同学随口都能说出好几个来,社区里的轮子一个接一个数不胜数。今天不是讲某个库的技术细节,而是跟大家聊一聊实现一个状态管理库的过程,以及我在这个过程中的一些思考。

背景

Erda 项目的前端状态管理,从最开始的 redux,到 dva,再到现在的 cube-state,也在逐渐跟着社区的趋势发展。redux 就不说了,dva 在我看来是一个优秀的库,设计思想挺符合个人口味。之所以会抛弃它转为自研的状态管理库,是由于 dva 是基于 redux 做的封装,而 redux 的字符串匹配形式的 dispatch action,天然就很难支持类型。在项目发展到几百个页面,近 2000 文件时,如果没有完整可靠的类型定义,对于后面的开发维护绝对是一场灾难

举个栗子,dva 中一个常见的 reducer 长这样:

dispatch({ type: 'products/delete', payload: id });

灵魂拷问来了:

  • 怎么确定 type 没写错呢?
  • 怎么知道 products 下的 delete 这个 reducer 是否还存在呢?
  • 怎么知道 payload 这个数据的类型是匹配 reducer 的呢?
  • connect 到组件的数据怎么知道是符合组件需要的类型呢?

目标

因为上述问题在没有类型定义时无解,且对于废弃代码不敢删除,担心哪里还在用,所以当时我们迫切地想找一个支持类型定义的状态管理库,同时为了避免改造太大影响正常业务开发,需要能平滑渐进地改造。

我们的目标很明确:

  • 有完整的类型定义链路,从 API 获取数据 -> 数据放入 store -> 组件从 store 取数据 -> 组件调用 store 的 effect 或 reducer,整个链路都有类型。
  • 兼容 dva,做渐进式改造,最好架构和 API 也很像,没有额外的学习成本。
  • 易于扩展,把项目相关的逻辑放在扩展中,保持库本身简单可靠。

过程

探索开源库

当时调研了许多库,但基本没有支持完整的类型定义链路,或者要切换到另一个体系上,改造难度很大,业务上的风险太高。我们也想过不如自研一个轮子,但在尝试过程中遇到些问题没解决,直到某一天发现了 stamen 这个库,里面利用 React Hooks 做监听和取消监听的方式启发了我们。

stamen 架构图

上图是 stamen 仓库中的架构图,可以看到结构和 dva 很相似,每个 store 里分 state、effects、reducers 三部分,通过 hooks 方式调用,在组件 mount 的时候注册 state 的监听,unmount 时移除监听。因为 hooks 就是普通的函数,很容易定义类型,对 Typescript 非常友好。所以 store 里的 state 结构类型、effects 和 reducers 函数的类型都可以很容易的获取到,如果组件也是函数形式,那整个类型链路就已经通了。

为什么我们没有直接用,而是基于这个又做了改造呢?因为在我们项目中基于 dva 做了一些增强功能,stamen 无法满足,同时这些逻辑并不适用于每个团队,所以不适合放在别人的库中。

无法满足的有以下几点:

  • 没有 key 或 name 属性,必须具名引入,某些场景下不方便。
  • 没有提供类似 dva 里的 subscriptions 能力,而路由监听是我们项目里很常用的功能。
  • 使用 dispatch(“action”, payload) 的形式其实 payload 类型是确定不了的,造成链路类型中断,而且不如直接调用 effect 或 reducer 直观方便。
  • 不支持在 effect 和 reducer 的前后加钩子函数,所以也没法支持中间件,比如 loading。
  • 不支持对 store 做扩展,比如加一个自定义字段,或者对 effect 做些定制增强。
  • 没有提供 state 的类型和类组件配合使用,类组件的 props 类型需要重新定义一遍。

基于开源改造

因此,在 stamen 的实现思想上,我们结合自身项目需要做了改造。

首先,结构和 API 上向 dva 靠拢,添加 name、subscriptions 字段,name 的作用后文会讲到,加了 subscriptions 后可以把路由监听、ws 连接等放在这里面,例如我们项目中常见的路由监听:
API 上,dva 把 payload 放在第一个参数里,把 call、put 等方法放在第二个参数里,这样限制了只能把所有数据都放在 payload 中传递,但有些其实可能不是接口需要的数据,比如 API 路径参数、特殊逻辑标记等,透传时还需要抽离出来会比较麻烦,如下图所示:
所以在 cube-state 中,我们把 call、select 等方法放在了第一个参数,第二个参数是 payload,后面还可以继续传其他参数,但调用时还是普通的形式,如下图所示:

然后,我们从 dispatch 方式改造为能够直接调用的形式,比如 countStore.effects.addLater(payload),这样类型定义就完美了,执行的地方必须传入 effect 定义的类型,而且 effect 内部也能直接调用 store 自身的 reducer。同时为了方便调用,我们还支持了展平形式的创建方式,把 effects 和 reducers 作为根属性,即 countStore.addLater(payload) 的形式。

接着,我们添加了 effect 和 reducer 的 hook,支持在前后执行一些逻辑,由此能够支持中间件系统。比如 loading 中间件,就是在每个 effect 执行前后自动更新 loadingStore 里的状态。这里就用到了 store 的 name 字段。
接下来,支持 store 的扩展。这个是为了支持一些自定义的逻辑,在我们项目中,前后端对于请求返回结构做了如下封装:

{  success: true,data: {},err: {}
}

一般只需要处理 data 字段,如果每个 effect 都从结构体里提取 data 会有很多冗余代码,最好在调用 service 过程中默认处理掉。因此支持了 extendEffect 用来扩展或覆写默认提供的 effet 第一个参数,比如我们项目中扩展了 getParams、getQuery 两个方法,覆盖了原有的 call 方法,在内部处理请求返回结构体,以及做提示的一些逻辑。

扩展后可以在 effect 中方便地获取路径参数、query 参数,以及一些成功、错误提示,比如下图中,获取路径上的 appId 作为请求参数,请求成功后给用户提示:
最后,把 state 类型暴露出来,在和类组件配合时不用再定义一遍
至此,大致结构就已经差不多了,后面增加的基本是一些深入配合具体业务场景的需求,比如支持基于一个 store 扩展另一个 store,支持全局单例模式等等。整体的架构图如下所示:

完善测试及文档

做一个稳定可靠的库,一方面是尽量简单,另一方面测试用例是必不可少的,所以我们也补充了比较全面的测试,基本覆盖到了每一个逻辑。并且,在后来发现 bug 时,我们也不断补充新的测试用例,这块是另一个话题,此处暂不细讲。

至于文档,因为本身很简单, 总共没几个 API,所以直接放在 README 里了。文档中提供了基础、进阶用法说明,也提供了在线 demo 供体验。

结语

在 cube-state 初版完成后,我们就逐渐开始在项目中做迁移改造,因为用法类似,除了补充大量的类型定义外,很多时候是比较机械的劳动。在打通了类型定义的完整链路后,项目的开发维护终于不再像以前那样,唯恐牵一发而动全身,能够避免很多因类型导致的错误。

当然现在也依然有些问题没有解决,比如扩展的 getParams 等方法没有类型定义,必须直接用 createStore 方法包装源对象的方式在某些场景下不适合等,我们也希望后面能逐渐解决这些问题,或者找到更好的升级方案。

后来也看到一些很简单优秀并且很相似的库,不过只有自己才知道自己的项目适合什么,这不是为了造轮子而造轮子,而是为了更好地支持项目的开发维护。所以,我们不做无意义的事。

之前听玉伯在分享时提到:其实在前端领域,还有很多基础的东西有待深入去做,比如像 webpack 这种打包工具,虽然已经很完善了,但臃肿难用的问题很难解决,如果谁能继续去“造轮子”,过程中探索出不一样的路,就是很有意义的。最后,愿各位在前端之路上,也能探索出自己的精彩。

欢迎参与开源

Erda 作为开源的一站式云原生 PaaS 平台,具备 DevOps、微服务观测治理、多云管理以及快数据治理等平台级能力。点击下方链接即可参与开源,和众多开发者一起探讨、交流,共建开源社区。欢迎大家关注、贡献代码和 Star!

  • Erda Github 地址:https://github.com/erda-project/erda
  • Erda Cloud 官网:https://www.erda.cloud/

灵魂拷问:我们该如何写一个适合自己的状态管理库?相关推荐

  1. [译] SpaceAce 了解一下,一个新的前端状态管理库

    原文地址:Introducing SpaceAce, a new kind of front-end state library 原文作者:Jon Abrams 译文出自:掘金翻译计划 本文永久链接: ...

  2. 教你写一个 React 状态管理库

    自从 React Hooks 推行后,Redux 作为状态管理方案就显得格格不入了.Dan Abramov 很早就提到过 "You might not need Redux",开发 ...

  3. 记前端状态管理库Akita中的一个坑

    记状态管理库Akita中的一个坑 Akita是什么 Akita是一种基于RxJS的状态管理模式,它采用Flux中的多个数据存储和Redux中的不可变更新的思想,以及流数据的概念,来创建可观察的数据存储 ...

  4. 一步步写一个符合Promise/A+规范的库

    Promise本意是承诺,在程序中的意思就是承诺我过一段时间后会给你一个结果. ES6 中采用了 Promise/A+ 规范,Promise 实现之前,当然要先了解 Promise/A+ 规范,规范地 ...

  5. 一步步写一个符合Promise/A+规范的库 1

    Promise本意是承诺,在程序中的意思就是承诺我过一段时间后会给你一个结果. ES6 中采用了 Promise/A+ 规范,Promise 实现之前,当然要先了解 Promise/A+ 规范,规范地 ...

  6. 用python写一个文件管理程序下载_Python管理文件神器 os.walk

    原标题:Python管理文件神器 os.walk 来自:CSDN,作者:诡途 [导语]:有没有想过用python写一个文件管理程序?听起来似乎没思路?其实是可以的,因为Python已经为你准备好了神器 ...

  7. 用Java写一个PC端的WIFI-ADB管理软件

    废话 一直在用android studio做android开发,偶然一次机会接触到了wifi adb,试了好几个支持android studio的插件,各有优缺点,有一个本来用着好好的,突然就用不了, ...

  8. 用go写一个docker(8)-介绍两个库cli和logrus

    有了前面知识就可以开始写一个简单的docker了,但为了让这个docker好看一点,我们先了解一下会用到的两个库:cli 和 logrus github.com/urfave/cli github.c ...

  9. 灵魂拷问:学Python搞一个云服务器到底能干嘛?

    很多同学很好奇,我为啥要买云服务器呢,这个问题我刚学编程的时候也没有考虑过,后来才知道,如果你没有玩过服务器,那你的Python技能还停留在模拟中,没有上战争玩一把真的.有了一个自己的服务器可以干很多 ...

最新文章

  1. Amoeba实现mysql主从读写分离
  2. 移动端placeholder不能垂直居中解决方案
  3. 学习js,尝试写一个表单验证框架(1)-规划
  4. java集合租车_Java入门第二季 租车系统
  5. 跨域解决请求限制(script标签)(热门搜索出现对应的词条)
  6. 优酷电视剧爬虫代码实现一:下载解析视频网站页面(3)补充知识点:htmlcleaner使用案例...
  7. 常系数齐次线性递推学习笔记
  8. ZooKeeper典型应用场
  9. java socket 双网卡_java获取双网卡ip地址
  10. should be mapped with insert=false update=false
  11. thinkphp视图中插入php代码
  12. 使用 Python 进行线性搜索优化
  13. Androidstudio加载工程很慢解决方案
  14. 基于R软件的网状meta分析
  15. ofd文件电子签章实现方法
  16. 【晶体管电路设计】四、共基极放大电路设计
  17. undo歌词中文音译_Undo - Sanna nielsen帮我看看这歌词翻译对么
  18. 华为p4不是鸿蒙吗怎么又改为安卓_华为已将“基于安卓10”变成“兼容安卓10”,EMUI就是鸿蒙OS...
  19. 汇智动力学员最新就业喜报,最高薪资16K!
  20. 基于ARM的嵌入式系统外围硬件设计

热门文章

  1. 辐射度量学(Radiometry)的基础知识
  2. java se 定时任务_Quartz 定时任务使用 —— 入门简单调用(一)
  3. 利用java和mysql数据库创建学生信息管理系统
  4. 触摸屏原理及程序框架
  5. 给中国学生的第七封信——21世纪最需要的7种人才(李开复)
  6. c语言if函数的应用
  7. 用于X射线束的掠入射聚焦镜
  8. 【Css】input输入文本框的样式大全和搜索框实例(图文和完整源码)
  9. 蒸螃蟹冷水蒸还是开水,冷水上锅蒸的螃蟹不掉腿
  10. 微软服务器安全补丁,微软发布10个安全补丁 修复26个漏洞