前端组件级别的抽象方向

基于2大主流前端框架下,前端的主要的工作其实就是在编写各种组件。似乎所有人都在说前端开发的天花板很低,除了一些特别的方向,很少有人在前端功能的开发上遇到过什么难题。这也导致没有人关注和讨论前端组件的抽象,大家都忙于做功能的开发。甚至有些前端开发在完全不做抽象的情况下仅用少数几个组件就完成了对整个SPA的开发。

为什么需要组件级别的模块抽象?
即使在掘金这样偏前端的专业社区,也有很多人认为不需要做模块抽象。以下是我的上一篇文章《如何把前端项目写成一座屎山?[1]》下的一个热门评论:

具体就不做反驳了,因为我根本不知道他在说什么!

模块化抽象的本质仍旧也逃离不了“高内聚、低耦合”这2个永恒的主题。更具体的,主要是为了性能和可维护性。但性能问题往往被直接忽略,因为现代浏览器的性能和框架的优化极大抹平了这部分差异。所以抛开一些情况下的性能优化,我们抽象的意义在于提高了代码的可维护性,带来了复用、逻辑内聚、稳定的开发效率、较小的心智负担和bugfree,所以这绝对是一件具有长期收益的事情。

抽象方向
代码行数
相信大部分前端开发都体会过接手别人代码,打开一个组件模块,代码1000+行那种扑面而来的压迫感。虽然我也看到过代码行数控制在小几百行,抽象依旧稀烂的代码。但暂时没见过组件超长但抽象仍旧非常好的代码。代码行数甚至称不上作为衡量抽象好坏的标准,但绝对是最接近本质的一个表象。就我个人代码风格而言,极少有封装超过150行的组件模块(代码风格可参考本人开源项目KerryCodes/leggo: 一款拖拽式前端表单生成低代码工具~ \(github.com\)[2])。

视图页面结构
这是最朴素的一个组件抽象方向,社区里讨论的也最多,按下不表。

单一功能
很多开发总是困惑什么时候应该开始抽象组件,是因为这个组件太大了代码行数太多了,所以需要去做拆分吗?是因为视图结构上相距甚远所以拆分吗?不是的,你应该要考虑的是功能。根据“单一职能”原则,我们可以基于功能去安排将哪些逻辑抽象成一个独立的组件模块。多思考功能级别的拆分,然后把每一个最小原子化功能实现的代码抽象成一个独立的组件。

复用公共部分
我们难免遇到一些功能相似的组件,导致在多个组件中重写了一部分相同或相似的逻辑。不要这样做,尽量把交叉部分的功能做抽象,达到复用的效果。这里的抽象可以是公共组件也可以是一个公共函数。如果复用和维护性无法权衡怎么办?维护性优先!不要为了复用一点逻辑搞一堆入参或处理大量的边界条件,导致代码冗杂难懂,这是不可取的。






自动初始化
这是一个非常常见的功能场景。假设你有一个新建弹窗,里面有一些表单在弹窗开闭的过程中需要恢复初始化值。最常规的开发就是通过手动去恢复初始值。但随着功能的迭代表单值的增删变化,我们不得不同时也不断去维护一大块逻辑用于初始化,这大大增加了开发的心智负担,导致维护性性变差。所以,我们的抽象在于使得状态初始化过程不需要手动去干预,而交给组件的加载和卸载过程自动去完成。

自定义hooks
相比于Class组件,函数组件提供了hooks,于是我们有了一个非常强大的带状态的抽象复用手段。明显的,我们可以将一些公共的业务逻辑抽象成hooks作为复用手段(当然也更应该首先考虑抽象成一个组件)。另外对于一些非公共的业务逻辑,只被某个特定组件使用,但是却比较复杂,比较偏过程,个人也会封装成hooks使用。这样做的好处就在于组件内部会比较干净,开发维护的心智负担下降。

但我们也并非可以滥用自定义hooks,因为hooks也有一些明显的缺点。比如副作用,隐式的状态,逻辑黑盒化等等,这些都增加了心智负担。(欢迎各位大佬讨论这部分内容!)

状态和重渲染
“状态”可以说是前端组件开发中最重要的一个概念,所有的重渲染都是来自于状态的改变。很多bug也是来自于状态与视图不一致的问题。状态维护的好坏标准在于状态的改变是否引起的是最小原子化的视图重渲染。如果一个状态的改变总是引起与该状态毫无关系的其它组件重渲染,那么基本可以确定这一块代码的抽象做的很差。这里可以特别提一下“纯组件”的优化,出现这种优化技巧的本质就在于状态改变所导致的不必要高成本重渲染。

有一种非常典型的抽象方式就是props.children,也可以归为这个方向。这个写法相信很多前端都了解,在面试过程中也是一个高频八股问题。但这种方式不应该被滥用,因为这会导致代码视图结构的清晰度下降。我总结了适用这种抽象的几个前提:页面视图结构导致父子组件无法分离;父子组件关联度很低;父组件的状态活跃,而子组件不活跃。本质上在于父组件的状态变化不引发子组件的不必要重渲染。

非受控组件
这个概念来自React官方文档介绍表单组件的部分,我们可以借用一下。如果你的组件总是需要接受来自父组件的传参,可能就是抽象不好的体现。越多的传参代表更多的耦合度。

软件设计有一个非常重要的原则叫“迪米特原则”,这个原则启发我们一个模块应当尽可能少的与其他模块发生相互作用。尽量使你的组件保持干净独立,状态在内部完成消化而不是受父组件控制。除了耦合度降低带来的bugfree,一个明显的收益是干净的非受控组件在复用时几乎是没有心智负担的。所以需要非常谨慎的使用状态提升这种手段,如非必要可以尽量避免。

申明:以上观点来自个人经验总结和思考,欢迎理性讨论和补充。另外所有方式都需要先考虑具体场景,不要无脑使用!

关于本文

作者:阿佛加德奔

https://juejin.cn/post/7156575298501214221

欢迎关注微信公众号 :前端Q