写点什么

如何设计一个优秀的组件

用户头像
Lee Chen
关注
发布于: 2020 年 08 月 06 日
如何设计一个优秀的组件

阅读更多系列文章请访问我的GitHub 博客,本文示例代码请访问这里



前言



在开发过程中,我们经常会遇到现有组件库无法满足需求,需要自己设计和实现组件的情况。那么,如何才能设计一个既满足产品需求,又易于开发人员使用的组件呢?本文就以一个级联组件的设计为例,探讨一下如何设计一个优质组件。



开始前准备



  1. 如果你想查看本文的 Demo 演示,请点击这里

  2. 如果想查看源代码,请点击这里

  3. 组件是用 React 实现的,如果你对它不了解,可以点击React 官网

  4. 本文涉及到的源代码都由 TypeScript 编写,如果你对 TypeScript 不了解,可以查看TypeScript 官网

  5. 本文 Demo 中的级联组件,是基于Ant Design开发的,如果你对它不了解,可以先查看[组件总览](https://ant.design/components/overview-cn/)。



接下来我将详细叙述一下组件的设计与实现过程。



组件设计



需求分析



开发这个组件的原因是来自工作中遇到的一个需求。



产品提出的业务需求是实现一个地区的级联选择,并且应设计师要求,不能使用 Ant Design 的Cascader 级联选择,因此需要自己实现一个级联选择。



通常需要从 4 个角度对需求进行分析:



  1. 业务需求角度

  2. UI 设计角度

  3. 开发人员角度

  4. 与其他组件配合角度



业务需求角度



在拿到一个需求之后,我们不应当直接考虑如何实现,而要先思考一下,这个需求是不是真正的需求。或者说,这个需求背后,是否还有更加基础和核心的需求。



现在,我们拿到了一个地区级联选择的需求,那么我们应该仅仅实现地区级联选择吗?我认为不是的,今天我们拿到的是地区选择的需求,明天就可能变成职业选择。



因此,我们实际需要实现的,应该是一个支持N 级的级联选择框。



UI 设计角度



既然已经确定了要实现 N 级级联选择框,就需要考虑其在各个屏幕尺寸的兼容情况,于是我在组件中引入了 Grid 栅格 。并默认设置了 3 级级联的样式,同时为使用者提供了相应的配置选项,如下:



<Cascade
rowProps={{
gutter: 10,
}}
colProps={{
xs: 24,
sm: 24,
md: 8,
lg: 8,
xl: 8,
}}
/>



开发人员角度



我们开发的组件,不止会被我们的团队成员使用,甚至会开源给其他开发者使用。那么,提供给开发者更好的体验就尤为重要。



为了节省开发人员的学习成本,我们可以按照以下思路考虑:



  1. 在满足基本需求的前提下,提供尽量少的 API。

  2. 为 API 选项提供尽量少的配置项。

  3. 为代码,特别是 API 提供尽量好的文档或注释。



定义的 Cascade 组件 Props 示例如下:



interface Props<T> {
cascadeKeys?: CascadeKeys; // 自定义 dataSource 中 value label children 的字段
value?: T[]; // 指定当前选中的条目
onChange?: (value: T[], level: number) => void; // 选中选项时,调用此函数
rowProps?: RowProps; // 行排列方式,可参考https://ant.design/components/grid-cn/
colProps?: ColProps; // 列排列方式
loading?: boolean[]; // 选择框loading装填
dataSource?: T[] | CascadeData<T> | T[][]; // 可选项数据源
}



可以看到, Props 中只有 dataSource 是必须的。也就是说,如果你对配置项并不了解,组件也只需要最简单的配置,就可以正常工作了,例如:



<Cascade
dataSource={pcaCascadeData}
/>



  1. 在使用 TypeScript 时,还需要特别考虑类型匹配的问题。例如可以在使用组件时,传入一个类型,并且在 onChange 事件中如果使用了其他类型, TypeScript 检查就会提示错误,如下面例子中的 PCAItem



<Cascade<PCAItem>
dataSource={pcaCascadeData}
cascadeKeys={pcaCascadeKeys}
onChange={async (value: PCAItem[], level: number) => {
setPCAData(value);
setPCAIndex(level);
}}
/>



  1. 对于级联组件,我们还需要考虑 dataSource 的数据来源可能有两种。



- 组件初始化时,就传入了所有的级联数据,例如 省/地/县/乡层级数据 。对应 Demo 中的“同步级联数据”,以及 PropsdataSource 类型定义的 CascadeData<T> | T[][]



- 在某些场景下,虽然没有级联选择框存在,但也需要处理树形数据,包括数据的查询和校验等功能,因此将该方法封装到 CascadeData 类中。

- 考虑到树的数据量可能非常庞大,如果在每次选择时都在树中搜索效率较低。因此设计成在组件创建时,直接遍历树中的所有节点,然后将每个层级所有节点的数据都存储在相应的 Map 中,之后就能很方便地查询数据。

- 虽然在组件初始化时遍历所有节点比较耗时,但考虑到用户从进入页面到操作组件有一定时间差,因此我认为这个问题可以忽略。

- 基于以上考虑,你在 PropsdataSource 类型中看到的 CascadeData<T> ,就表示直接传入了一个 new CascadeData(treeData) 。而T[][]就表示直接传入树形数据,由组件内部进行 new CascadeData(treeData)



- 组件初始化时,只传入了第一级的选项,之后每级的选项通过前一级所选择的参数,从服务端获取。对应 Demo 中的“异步级联数据”,对应 dataSource 类型中的 T[]

- 组件初始化时,只传入第一级的数据,其他级别传入空数组,如:[[{"code":110000,"name":"北京市"}],[],[]],组件会渲染出 3 层级联选项。

- 当进行选择时,需要使用者通过 onChange 事件自行更新下一级的数据。也就是说,组件完全放弃了对数据的控制。



与其他组件配合角度



由于该组件需要与 Ant Design 其他组件配合使用,如前面讨论过的 UI 部分,该组件就结合了 Grid 栅格 组件。既保证了该组件在各屏幕宽度下正常显示,又保证了与其他组件的显示一致。



除此之外,还需要考虑与 Form 表单 组件的配合,特别是兼容表单校验功能。



结语



本文通过一个级联组件的设计案例,探讨了如何从四个角度分析,进而设计一个优秀的组件。这 4 个角度分别是:



  1. 业务需求角度

  2. UI 设计角度

  3. 开发人员角度

  4. 与其他组件配合角度



我认为很多时候组件的设计并没有最优解,总是需要根据需求在各种方案中取舍。但只要按照本文提到的 4 个角度进行分析,就能设计出优秀的组件。



发布于: 2020 年 08 月 06 日阅读数: 99
用户头像

Lee Chen

关注

还未添加个人签名 2018.08.29 加入

还未添加个人简介

评论 (1 条评论)

发布
用户头像
推荐
2020 年 08 月 07 日 14:29
回复
没有更多了
如何设计一个优秀的组件