基于 React 的组件化开发方式,为富前端 web 应用提供大量技术实践,社区逐渐形成了稳定的组件规范,本文从 API 层面归纳出 6 种组件类型,分析其优缺点和适用场景,为日常组件开发提供一个方法指南。6 种类型分别为结构型组件、样式型组件、组合型组件、配置型组件、受控型组件、非受控组件。
结构型组件与样式型组件
结构型组件定义了组件大体结构,结构的具体实现由外部传递。样式型组件确定了组件结构细节,外部只需传递参数即可渲染预期样式。样式型组件是较为常用的组件类型,很少有开发者会根据一份设计稿来推断组件未来可能的改动,这也导致了样式型组件在复用性与拓展性上偏弱。对于比较通用的组件,例如 Button 按钮、Modal 弹框、Form 表单等,不应仅提供样式型实现,应该抽象出结构型组件。
这两种类型并不是非此即彼的关系,样式型组件固定的 API 参数可以降低使用成本,结构型组件弹性的 API 设定可以提供扩展性,结合两者的优点可以构造出既简单又可拓展的组件。关于两者结合的优势最具说服力的实践是通用组件库,结构型组件可显著降低业务方的沟通成本与接入风险,如下示意图演示了业务方与组件库之间的两种沟通模型:
以上两种模型每一个箭头为一个工时,样式型组件库完成一次需求变更需要三个工时,业务方要等待组件库实现功能后才能进行下一步。结构型组件库给予业务方更多的自主性,不用等待组件库实现新特性,通过自定义结构满足当前需求,组件库有充足的时间分析需求是否通用,是否值得提供新 API,结构型组件在这个过程中扮演了缓冲区的角色,使得业务方与组件库可以并行协同开发,确保各自的研发效率与节奏。
组合型组件与配置型组件
组合型组件以 JSX 为主体,通过组件间的嵌套组合描述业务逻辑。配置型组件通过 props 传递数据结构,组件内部根据预先设定好的逻辑渲染视图。日常开发倾向于写配置型组件,组合型组件更多的出现在通用组件库中。
组合型组件结构清晰,扩展性高,组件使用者通过阅读 JSX 的 render 函数即可了解业务逻辑,但组件间联系微弱,ref 引用相互隔离,难以构建复杂的交互组件。配置型组件需要写的代码量少,但组件内部渲染处于黑箱,使用者难以理解组件逻辑,使其在拓展性上偏弱。比较基础的组件,例如 Form 表单,Select 选框等,建议采用组合型,有利于使用者组织业务代码,复杂交互组件可使用配置型。
组合型组件最具代表性的实践是 Ant Design,整个组件库的 API 设计严格遵循组合型优先原则,为同一组件的不同位面分别提供组合型结构,使其在拓展性和易用性上都达到了很高的水准。如下示意图演示了用两种组件类型开发 Select 选框的演化模型。
受控型组件与非受控组件
这两种类型有另一种表述:无状态组件和有状态组件。受控型组件内部只负责展示,仅对外提供回调,以表达改变的期望,其最终行为完全由外部驱动。非受控组件由内部处理某些行为,并不强制外部状态同步。官方推荐输出无状态受控组件,但是有状态的组件在项目开发中仍是必要的。
受控型组件在自身层面规范了单向数据流,可以与其他数据层框架整合,但是开发一个复杂的受控型组件,开发者可能需要向外提供数不清的接口与回调。非受控组件较为智能,组件可以自主维护状态,但开发者常常因此懒于做状态同步,上层组件重新渲染时,非受控组件会丢失内部的状态,失忆,日常开发中大多数的 bug 因此而来。
我们经常会以内部是否拥有 state 来衡量一个受控型组件与非受控组件,但是完全遵守这条标准将很难提供一个简单易用的大型受控组件,所有状态都由外部控制,使用者需要写大量配置代码才能跑通一个大型组件,使用成本极高。官方提供的解决方案通过两者结合的方式来处理受控与易用的矛盾,如下示意图展示了一个 Input 组件可以接受的参数类型。
开发一个受控与非受控兼具的组件,对组件本身的开发与维护有更高的要求,其难度随组件本身复杂度的增加而增加。但是对组件使用者来说,这种两者兼具的组件最能适应快速开发与后期代码调优。任何有输入输出特性的组件(各种表单,配置 + 回调组件),都可参照上述类型定义提供 API。
总结
React 组件本质上是 JS 函数的另一种形态,一切与函数有关的思想都可以反映在组件里,每一种组件都有其适用场景,开发一个大型 Web 项目需要搭配使用不同类型的组件,如何做出合适搭配则需要长时间的开发积累,在真正的项目里寻找最优解。