为何要进行组件设计
开发过程中有大量的重复或相似模块,通过对数据和方法的简单封装,可以解决许多代码冗余的问题。特别是随着项目规模和复杂度的增加,良好的组件设计能够节省许多时间精力和开发维护成本。
如何提升组件易用性
合理的封装组件
前端组件按照类型可以简单分为容器组件,功能组件和展示组件,一个优秀的组件应该保证:功能内聚、样式统一、并且与父元素仅通过 props 通信
组件的封装粒度并不是越小越好,很多时候一个组件是在其他一个或多个组件的基础上开发的,无法完全以功能点的数量衡量是否遵循单一职责原则,组件开发者需要根据组件功能和目标来确定组件封装粒度:
- 当该组件需要承载具体的额外功能时,相较于新增 API ,封装成独立的组件是更好的选择
🌰 当我们在多个地方都要使用选择电池电压的组件时,就不适合在选择器组件的基础上加功能,而是包装成一个新的组件
- 当组件中存在可能被单独使用、可以承载独立功能的子组件时,可以将其以内部组件的形式提供。
🌰 如下图的列表卡片本身是一个组件 Card,其中框中的主体部分在其他的地方又有用到,这时候,组件就可以做成类似 Card.Main Card.address 这种形式调用
规范的 API 编写
一个易用的组件,使用者无需阅读文档或仅快速浏览文档即可上手使用,并且应当在使用过程中给予清晰的注释和代码提示。希望以下 API 编写建议能够给组件开发者一些参考:
减少必填的 API 项,尽可能多地提供默认值,降低组件的使用成本;
使用通用且有意义的 API 命名:
onXXX:命名监听/触发方法
renderXXX:命名渲染方法
beforeXXX/afterXXX:命名前置/后置动作
xxxProps:命名子组件属性
优先使用常见单词进行命名,如:value、visible、size、disabled、label、type 等等
单独维护类型文件,并将其打包至组件产物包中,这样使用者在开发过程中能够实时看到对应的类型提示;
在类型文件中,为 API 编写注释;
[Slot] 与 [Props] 的选择
有的组件中会有大量定制化的场景,这种时候,通过传入类型 props 来判断定制化内容就会让组件看起来非常杂乱,类似这种场景,建议用 slot 替代组件中的定制化内容
什么是插槽(Slot)?
Slot 是 Vue 框架提出的概念,可以理解为临时占位,可以用其他组件进行填充,Slot 能够实现父组件向子组件分发内容的功能。Vue 框架中提供了
使用
props.children
获取子组件,若需要区分使用不同子组件,只能通过数组下标读取。使用 Props 传递 ReactNode 元素。
将组件划分为多个内部组件,交由开发者自行组装。
如何提升组件可扩展性
开闭原则:对扩展开放,模块的行为可以被扩展;对修改关闭,模块中的源代码不应该被修改
将 DOM 交予用户接管
在前端组件中,应该提供对应的 API 属性或方法来支持额外的功能,给予开发者更充分的扩展空间,而不是有部分需求无法满足时放弃使用组件。
形式上比如 FlatList 中的 renderItem, 使用者可以根据各自的需求来自定义渲染的内容
与此类似的,还有 renderFooter
、renderOption
、renderFormat
等 API,这些 API 实现难度并不高,一定程度上将 DOM 元素的掌控权交予组件使用者,作为通用组件,为开发者提供了部分功能和样式的可扩展性。我们在设计前端组件时,多多留意组件中能够接管给使用者的渲染逻辑和操作逻辑,并将这些逻辑暴露出去。
设计可扩展的 API
组件开发前,整理组件所需实现的功能,并以功能为维度设计组件 API。以下是一个设计移动端选择器的例子,这个选择器需要支持单选、多选和时间选择,于是这样写了第一版 API👇,它可以满足我们当前的选择器需求。
在后续迭代中发现还有地区选择和级联选择的需求,选择器需要进行优化更新,在以上 API 的基础上只能通过添加 cascader、region 两个布尔值字段用于标识不同选择需求。这样做的缺陷是很明显的,每当我们新增不同类型的选择功能,都需要新增一个 API 字段,并且这些字段还是互斥关系。理清问题后,更新了第二版 API👇。在组件库中很多 API 都设计为常量枚举值的形式,即使其只有两个取值,这样扩展性相较于布尔值类型更好。
除上述例子外,还可以利用 ts 的泛型和可选类型来实现 API 扩展,例如 Table 组件的 pagination、border 等字段,既可以直接设置为 true/false,也能够以对象的形式进行更详细的配置。