对于简聊 React 的一些回忆和反思(初稿)

看到钉钉的功能愈来愈多了, 前段时间忽然想起来之前简聊的事情来.
当前公司跟钉钉的一些风声, 具体也不清楚, 到不少年后才听到了收购的事情.
Slack 具体的玩法我并不清楚, 可是钉钉当前延伸出来的功能给我一些感触,
当年简聊在功能的扩展来讲缺了太多围绕聊天的扩展创新, 玩法也不温不火,
固然若是当时有条件一步一步有条理地往外扩展, 也说不许后面是否是会有机会.
我当时也基本上一心在处理 React 的事情, 极少对整体的产品形态提出想法.css

单纯从前端代码来讲, 咱们当时也算很早尝试 React 而且稳定下来了.
通过这些年, React 改变也很多, class 组件到今天用的人比较少了,
并且随着我到不一样的公司遇到不一样的场景, 代码的侧重也改变了很多.
再当时来讲有些东西看着是迷雾, 还要不断前行探索才能知晓,
如今回头看的话, 是比起从前清晰了不少, 那些说是弯路说是妥协的东西.前端

CoffeeScript, ClojureScript, TypeScript

CoffeeScript 在当年算是 alt js 当中最成熟的一门语言.
就功能来讲, 它仅仅是让人更容易些 js 的语法糖而已.
跟 ClojureScript 相比, CoffeeScript 没有原生的不可变数据支持,
跟 TypeScript 相比, CoffeeScript 没有类型系统加持.
上面说的这两个 ReasonML 都有, 惋惜到如今 ReasonML 给人感受都还不够成熟.react

就功能来讲, CoffeeScript 确实是残缺的,
当时虽然简历了代码规范, 可是很大程度仍是依靠人自觉把格式统一块儿来.
而如今, 不管 ClojureScript 仍是 TypeScript 都是经过自动化格式工具写的,
Prettier 的使用基本追平了的 CoffeeScript 带来的编写效率的提高,
并且就团队代码习惯来讲, Prettier 比起 CoffeeScript 固然是好多了.
不可变数据的残缺, 致使了一系列的奇怪问题, 后面再说.
没有类型系统确实也不如 TypeScript 适合多人团队.git

不过当年 ClojureScript 跟 TypeScript 比较仍是不多出现的,
我印象里公司却是有那我的在研究 TypeScript 这些了,
而 ClojureScript 在当时工具链也还有各类问题, 即使今天也有一些.
不过即使是今天, 因为招人的缘由还有工具链特殊, 我仍是不以为能在多人开发当中用起来.
ClojureScript 更多的, 仍是让我站在 FP 语言的角度审视 js 生态缺失的功能.程序员

Immutable 和 Immer

前面说的 CoffeeScript 或者 js 没有原生不可变数据的问题,
按照 React 的设计, 不可避免须要用到不可变数据, 不然优化起来会很绕.
如今来讲, 有了 Hooks API, useState 把数据一份份拆开了,
这样每一个数据更新的时候, 算是经过替换达到了不可变数据的效果,
可是也存在过于分散且触发屡次更新的问题. 仍是有时候须要不可变的对象来处理.github

当时用 Immutable.js 的问题主要就是学习成本,
我最开始从了解这个东西到熟悉了敢用在项目当中也花了不短的时间.
除了写法比较绕之外, 对于前端来讲这也是比较陌生的一种用法.
后来即使我用了, 我印象里同事仍是有很多的抱怨的, 比较维护着累.
这东西并非不能掌握, 可是当在 js 当中写起来就是挺烦的.
并且对于第三方代码来讲, 只有 JSON 对象时它们接受的数据格式.
那么 Immutable 数据就要在咱们代码当中转来转去, 维护就很累.redux

我后面用 ClojureScript 就不是这样的. Clojure 默认用不可变数据.
这样我在开发当中几乎无时无刻不是直接用不可变数据在编写逻辑,
就是说没有多少须要转换的场景, 脑子里对数据的理解统一并且清晰.
Lodash 当中 updateIn setIn 就是 Clojure 中很日常的用法.
这些 API 在 Immutable 当中更是比比皆是, 只是说 Clojure 作到了语言核心中.小程序

如今咱们用的是 Immer, 语法比较贴近 JavaScript 原生的操做.
就推广使用来讲, 得益于语法简单, 并且加上 Hooks API 封装, 容易多了.
不过从 review 的结果来看, 仍是偶尔会出现遗漏.
Immer 毕竟是用了 freeze 强行设定数据不可变, 但看着输出跟 js 又没区别.
就无法保证在场景当中串来串去不会漏掉. 固然也还比如较少.segmentfault

不可变数据这个事情, 四五年了, 萦绕在 React 社区仍是没有消散掉,
我以为这个做为长期的一个方向, 也不会很容易就有完美的结果了.
若是说期待, 我但愿 ReasonML 到时候能把这事情统一掉.
由于 js 老是要兼容老代码的, 无论怎样引入不可变数据, 心理负担总会在.后端

Less 和 emotion

简聊用的 CSS 预编译方案是 LESS, 当时 teambition 统一的习惯.
总的来讲我对这种层层重置的 CSS 规则也没留下多好的印象.
当初 Vjeux 那个 CSS in JS 的演讲发布的时候我是深有感触的,
用了两三年的预编译, 大部分的局限性基本上也碰到了,
我是很期待直接用上 CSS in JS 的方案, 作细粒度的包含代码逻辑的控制.

就当时来讲, 我以为方案是不够成熟的, styled-components 也以为走得太怪.
在我本身 cljs 的方案里边, 我后来用的是 inline styles, 局部场景够用,
而 inline styles 最主要的问题是浏览器前缀和伪类的控制, 无法用在产品当中.

后来在朋友当中了解到 emotion 的方案相对成熟, 我就尝试了一下,
我最后仍是没有用 styled-components 那样的写法,
对于 emotion 我只是当作定义 className 写法来用, 倒不是新版本官网推荐的用法.
若是参考官网, 反而咱们的写法显得并不规范, 也很差配合官方 Babel 的优化.
只能说堪堪到了一个解决掉我认为痛点问题的状态,

let styleA = css`
  color: red;
`

let styleB = css`
  background-color: blue;
`

<div className={cx(styleA, styleB)}>red</div>

可是这也倒是帮我规避了不少的问题, 也由于是变量因此很好用代码进行控制.

问题主要在团队当中, 以往 LESS 的写法是你们都熟悉的, 有一套习惯.
加上 emotion 官网推荐的是 styled-components, 就形成了不统一.
而个人话没有多少动力去推进这方面的改变了, 这些涉及到不止一个改变的地方.

Mixin 和 Hooks

简聊遗留的代码中组件复用的代码使用的是 Mixin.
实际上能抽取出来的复用的方法并很少, 并且维护性并不算好.
在 Class 上动态注入的方法, 维护当中基本靠约定, 不容易排查.
Mixin 的用法后来对着 createClass 被废弃也跟着废弃了, 改成单继承.

中间用高阶组件进行逻辑抽象的事情你们都经历过了,
如今的结果也都知道了, 随着 Hooks 出来, HOC 基本没人说了.
当初 HOC 咱们也试着写过, 我仍是参考着同事的代码写的,
但我真心以为 decorator 的代码很容易写出问题来, 并且我很容易漏掉各类东西.
就逻辑复用来讲, HOC 主要是类库做者用, 业务开发几乎不会去乱搞.

如今有了 Hooks 再去审视当初的 Mixin, 就以为很原始.
Mixin 几乎只是粗暴地讲方法一个个剥离到单个的文件, 事情只是作了一半.
饭馆 Hooks 封装的逻辑较为完整, 复用的场景也多, 也认为较为可靠.
就咱们如今来讲已经大量使用 Hooks 用在业务抽象当中了,
因此 Hooks 是实实在在对于产品业务有不少帮助的, 而不限于类库开发者.
回顾简聊的代码, Mixin 也算抽了二十多个吧, 这数量是不如 Hooks 的.

Props 和类型

简聊代码当中主要用 PropTypes 进行的组件参数的管理.
后来这些东西渐渐废弃掉了, 有点不知不觉的.
如今应该大部分人都使用的 TypeScript 声明组件的类型了吧,
有了类型的辅助, 即使没用动态的参数校验, 也很难出现那方面的错误.
就这一点来讲 TypeScript 为前端带来了不小的改变.

actions-recorder 和 respo-reel

action-recorder 我在前面有留文章详细讲了, 是我改出来的名字.
原型的话, 是 Elm 的 hot code swapping 吧, 还有 Redux 的调试工具.
当时具体时间我记不清了, Elm 那些 Demo 你们应该都看过, Redux 也有些风声,
加上当时社区有很多人在追这方面的工具链, 我也就本身作了一些尝试.
actions-recorder 最初由于实现的问题, 在内部试用性能还挺差, 不过还好立刻修复了.
actions-recorder 是为了简聊定制的类库, 基于 Immutable.js , 算是耦合了.
后来 Redux DevTools 大概作了更完整的版本, 我不清楚用的人有多少.

actions-recorder 的核心原理是全部的 actions 都存储下来,
后续能够经过切换 actions 和 updater 从新计算, 回放整个应用的状态.
这同时也推导出一个要求, 就是 didMount 等等操做时, 不能够发出 actions.
这个要求当时对我形成了不小的影响, 毕竟在 didMount 时 dispatch actions 挺常见的.
为了迎合这一套方案, 我花了不小的心思对相关逻辑进行了不小的改造.
特别大的一块就是切换也没请求数据的行为, 都跟 didMount 脱离了.

actions-recorder 这套重构当时也是也没有太多的难点, 逐渐就完成了.
从效果来讲, 我认为仍是不错的, 请求从 didMount 分离, 就是从 render 分离,
数据在路由切换前就开始请求, 时间上提前了一些, 界面上也避免了多个分离的 loading.
可是从坏的一面来讲, 这种约束对于后续的开发维护来讲是不友好的.
我印象当中我同过后面补代码的时候比较容易会破坏这个规则,
并且有了这些限制, 设计逻辑也累了许多. 谁不喜欢在 didMount 直接请求数据啊.

站在 actions-recorder 的角度, 回放 actions 好处比较明显,
当时有几个很差排查的 bug, 在 actions-recorder 的工具当中很容易定位,
由于每一个 action 先后的数据状态都在, diff 能够直接看, 很明确.
可是一旦 didMount 时会发送 actions, 就致使回溯时总会有重复的 actions 发出,
这些 actions 也很差追踪, 那么调试的方便就被破坏掉了.
这些好处对我而言有用, 可是对团队来讲却未必真的好, 如今看看确实问题太多.

后面我用 ClojureScript 写的代码当中, 对于 Respo 的小应用, 时间起来比较轻松.
因为 Respo 我在设计实现时基本上杜绝了 didMount 发 actions 的可能性,
因此自然就的是知足前面的约束, 而我用 respo-reel 类库很容易作到这一点.
另外 respo-reel 跟 cljs 纯函数的特性配合, 更新 updater 的效果也比较好.
某种程度说这个就是另外一套独立的技术路线了.

就目前公司中而言, 我彻底放弃了 actions-recorder 的方案(也没有用 Redux 那套).
当存在大量的伴随 didMount 进行局部的 actions dispatching 的时候,
actions-recorder 的方案就显得没有多少意义, 特别是还跟 Hooks 相关联.
actions-recorder 也要求数据尽可能存储在全局, 这一点在当前的场景也不合适.
特别是我的开发大量的子页面, 各自有各自的数据, 跟简聊的场景就很不同了.
虽然我最初有考虑复用部分 actions-recorder 的思路过来, 但仔细想总没有多少好处,
只能说是场景不一样, 加上那些前车可鉴吧.

脱离 Elm 的全局状态的方案的话, actions 对我来讲就没有太大的意义了.
若是是 ReasonML 那样经过类型能定义 action 方便作 switch 的话倒还好,
在 TypeScript 中用 Action 效果并很少, 并且还多一层抽象.
反而不如直接用方法去操做那少有的全局状态来得好维护了.

全局数据的使用

简聊的场景比较特殊一些, group 和 messages 是全局的数据, user 也是.
一方面 WebSockets 会推送新数据过来, 这些默认就是全局处理的, 而不是局部,
另外一方面聊天应用就是围绕消息和用户展开的, 消息全局存储也有意义.

目前公司的场景是各类页面表单图表基本上是各自从服务器请求数据,
虽然形态上是单页面应用, 实际上子页面之间或者全局复用的数据能够说少得可怜.
这样大量的状态也就是在各个组件本身去作维护了, 而且状态也很是多.
截然不同的场景. 固然状态不少对于调试来讲也不大友好. 可现状就是这样.

router-view 和 ruled-router

因为前面说的 actions-recorder 的限制, 就要去全部全局状态都要在 store 统一维护,
比较重要的一块状态, 就是路由状态, 当时我也走到了 redux-router 的前面.
我设计了 router-view 模块, 改变了路由在 React 中的角色, 使用全局数据控制.
这是个定制化的方案, 也不必展开说了.
另外这个 router-view 跟后来 Vue 说的 router-view 还不同, 具体也没看.

不过有一点是后面影响我比较深的, 就是我认为 react-router 界面和路由耦合颇有问题,
router-view 是定义 JSON 的路由规则对路径进行解析, 而后交给页面渲染,
这根 react-router 经过 Context 层层处理的方式彻底不同.
router-view 应该说更接近于后端解析和处理路由的方式, 先解析, 再当作数据处理.
解析完成之后, 处理数据对于 React 来讲是很是清晰也很好排查问题的.
这个在 react-router 那样的耦合严重的方案当中就显得分散并且琐碎了.

这个思路致使后面为了解决公司面对的嵌套层级过深难以维护的问题时, 我直接选择了改换路由.
ruled-router是从 router-view 延伸的对于嵌套路由作更深层适配的方案.
后面发现配合 TypeScript 的类型还能作不少的定制, 就继续深刻作了代码生成.
关于细节, 前面发文章讲过, 再也不展开了.

但这边也还有个问题, 就是定制过深以后, 整个方案彻底是自成体系了,
这个对于招聘进来的新人来讲, 首先有一个学习门槛的问题,
再者虽然跟 TypeScript 作了配合, 可是有些地方并不很直观, 有一些思考的成本.
这个毕竟不是社区大量的投入打磨的方案, 也没有大量的文档教程支持, 致使局面有点奇怪.
若是是 Facebook 或者阿里, 搞出一套方案, 是有能力往整个社区推广的,
而咱们做为小厂, 本身用就是本身的培训成本, 彻底须要本身操心了.

请求的抽象

请求这个坑也算是针对简聊的场景专门设计的方案, 来源是 Netflix 的 data graph.
Netflix 具体细节我记不清了, 当时有个演讲专门说的.
思路跟 GraphQL 相似, 也是方案自动处理请求的依赖关系, 前端把相关依赖整合起来.
当年 GraphQL 很是不完善, 并且没有跟对简聊作数据推送的场景,
通过这么多年了, 社区不少公司跟进了 GraphQL 的方案, 作了不少探索.
我所在的公司后端是 Go, 并且业务较重, 我这边也就失去了跟进 GraphQL 的可能.
当前使用的方案较为原始, 可是也尽可能在用生成 API 代码的方案配合 Hooks 作一些抽象.

简聊当时的数据依赖的抽象很稚嫩的, 也是由于场景简单, 因此走下去了. 可维护性也通常般.
从后面 GraphQL 的路线进行对比, 很明显, 数据抽象这个事情须要后端帮忙.
后端微服务之间很容易经过多个请求进行数据聚合, 性能优化办法也不少.
这些等到前端再想办法作抽象的, 已经算太晚了, 限制也不少, 效果也很弱.

但整体说我认为数据自动处理依赖在将来也还会是会被常常触及和深刻挖掘的场景.
GraphQL 不是最终方案, 至少咱们的场景就不够用. 固然更多仍是靠后端了.

Form 场景

简聊由于是聊天室, 当时对于 Form 的需求不多, 或者说只有特定的一块,
当时的需求是后端想要经过 JSON 配置表单, 前端渲染就行了.
因此当时给出的也只是 React 根据 JSON 作简单的 Form 响应的方案.

后来到了别的公司, 接触到后台管理大量使用 Form 的场景, 我发现本身忽视了这整理一大块.
我这边后来开始了 meson-form 模块继续针对业务场景定制 Form 的方案.
这就跟简聊当时面对的简单的场景很不同了, 并且 Form 后面还会有各类重逻辑的变种.

匆匆忙忙和代码重构

简聊那段时间让我造成了一个观念, 就是架构调整不多会被分配出时间来作,
因此基本上就常常要咱们本身私下调研方案还有见缝插针一点点去作了.
在执行层面上就基本上这样了, 并且要一点点拆成小的任务, 否则很差推动.
而大的重构并非说没有, 可是时间上只能尽可能本身去匀本身去控制了.
而当时因为 React 生态各类东西不成熟, 并且有时候直接破旧立新, 就比较被动,
我为了能争取到主动, 花了不少心思, 可是最终反而开始脱离大部队的倾向.
如今的借我就是我倒向了 ClojureScript, 不少方案脱离社区更加严重了.

我在饿了么的精力, 如今我也不太肯定, 大公司对技术研究和投入能作到怎样的节奏.
就我在小公司的话, 因为场景和业务相对单一, 我要花很长时间面对一样几个问题,
因此我能够花不少零碎的精力定制出相对深刻的方案, 考虑的场景也单一一些,
而在具体操做的要尽可能考虑和已有方案短时间共存长期尽可能替换的思路,
在后续的开发当中穿插着逐步把老的方案和实现一点点替换掉, 或者干脆不替换.
这个在具体实行的时候是很是琐碎的, 但涉及的代码也不会少.

后面我看到社区别人给出的方案, 特别是大厂给的方案, 封装作得较为完善甚至到多余.
就技术方案来讲, 我不多能给出那么完善的方案, 由于打磨的时间和需求都相对少,
个人方案服务的人群也没有那么多, 公司也匀不出那么多时间让我专心去作.
但换个角度说这样持续演进并且封装不完整的方案, 对于他人接手来讲显然并很差.
这个方案须要我持续 review 和提醒保证执行不出错, 而不是别人比较容易加入维护和扩展的.
这些事情提及来是要我做出转变, 规范和严谨, 但在个人角度条件又不够充足的.
未来说若是我去了大厂的话, 是否会有足够精力在一块东西上打磨呢, 我内心是有问号的.

但如今再换个角度来讲因为我大量基于 ClojureScript 的研究和展开, 能够说自成一体了,
这些方案和 JavaScript 内在的那些偏离还有纠葛, 也让我操不少的新,
操心的结果对于 ClojureScript 受众之外的人群可能还不大被理解,
就这个事情算下来也是让我比较有的头疼的, 也不大乐意到被纠结在这个上边.

其余

简聊的代码多年前我就没参与了, 并且公司转向其余方向, 后面你们都知道了.
我当初离开的时候除了原来几个缘由, 也由于当时 teambition 仍是 Backbone 为主,
我心理上比较反对在享受了 React 的方案以后再去体验一遍 Backbone 开发的痛苦,
并且在 FP 路线走得太远特别是孤立行走太远的状况下,
加上 leader 们也不会给我足够支持去推进 React 方面的事情, 我也有点以为无力.
后来 teambition 转 React 的事情知道的人不少, 中间的坎坷也很多,
当时我在一家用 Vue 的公司本身心思也在 ClojureScript 上边, 只能远远围观了下,
虽然过后有打听过, 可是也没机会跟前同事深刻去谈那些东西, 后面关联的少了.

平心而论虽然简聊场景脱胎于 teambition, 可是后者数据规模复杂太多,
原先积累的那些工具链和方法论, 是否能在其中应用, 我内心是没底的,
并且 Lisp 程序员这种跟着场景作定制的心态, 到了不同的规模给出的方案变化也会不小.
虽然参与的有很多当时简聊的成员, 但我也不认为个人方法论有留下多少.
从后面的研究看, 那些方法论跟 ClojureScript 的契合度只多很多,
即使我是在快离开的时候才渐渐整我的倒向 ClojureScript 阵营的...
如今不少的思路在 Respo 相关工具链中还存留着, 我的项目也还在, 但毕竟变化很多了.

另外那些延续到了后面公司的用法, 随着配合 TypeScript 的努力, 也改变了很多,并且从后面接触的大量后台管理的表单的场景看, 简聊的单页面才是比较特殊的场景.再说后面我也更多接触到前端场景的复杂度了, 甚至移动端和小程序我都还没开始...除了小程序, WebAssembly 那边的事情才开了个口.. 前端真是好复杂.

相关文章
相关标签/搜索