序文
まだ新人で職場に入っていたのを今でも覚えています。面接に出かけると、いつも部品を開発するかと聞かれました。当時、プロジェクト開発では、最初はElement UIであり、後にAntdに置き換えられた既製のUIコンポーネントを使用していました。どのコンポーネントを変更しても、開発時間を大幅に節約できます。通常のコンポーネント開発では、タイトル、ポップアップウィンドウ、およびテーブルの単純な2次カプセル化が実行されます。一言で言えば、コンポーネント開発の「実行」はまだ浅いので、インタビュー中の自信はわずかに不十分です。
長年の蓄積と経験の蓄積の後、UIコンポーネントのセットを自分で開発することはもはや難しくありません。Antdのソースコードを開き、Antdの技術チームが公式Webサイトに表示されているコンポーネントをどのように実装しているかを調べたいと思います。
私の子供時代と無知の話をしましょう。私が最初にElementを使い始めたとき、それはかなり混乱していました。お腹が空いたのではないかと思いましたが、お持ち帰りではないですか?なぜまだテクニカルコンポーネントライブラリを提供しているのですか?彼らの技術チームも非常に強力であることを私が知ったのは後になってからでした。
「汎用コンポーネントを作成しましたか?」
このインタビューの質問の鍵は、一般的なコンポーネントの書き方です。
システム特性
現在、UIコンポーネントライブラリは豊富で成熟しているため、日常の開発では、一般的なコンポーネントが作成されることはめったにないと思われるかもしれませんが、そうではありません。
すべてのシステムは、ビジネス機能、インタラクティブ機能、UI機能のいずれであっても、タイトル、ページレイアウト、リスト、編集可能なテーブル、あいまい検索ボックスなど、いくつかの一般的なコンポーネントを分類できます。
例としてリストを取り上げます
Antdには既製のテーブルコンポーネントがありますが、実際の開発では、一般的なリスト管理ページに検索項目とデータ表示があり、検索リセットボタンまたは検索エクスポートボタンがある場合もあります。
したがって、一般的なコンポーネントが役立ちます。パッケージ化すると、1つのコンポーネントで何千ものリスト管理ページを完成させることができます。
{!resetAble && (
<Button type='primary' onClick={handleReset}>
重置
</Button>
)}
{exportable && (
<Button type='primary' onClick={handleExport}>
导出
</Button>
)}
<Table dataSource={list} columns={columns}/>;
ユニバーサル機能
日常の開発におけるいくつかの機能は実際に一般的なコンポーネントにすることができることが知られていますが、一般的な境界をどのように定義するのですか?
一般性が高すぎると、コードが複雑になりすぎ、一般性が低くなり、開発効率が低下します。私は一般的に次の2つの点を観察します。
-
この機能を使用する場合、ビジネスとはあまり関係がない場合があります。編集可能なフォームなどのビジネスラインでは、UIまたはインタラクティブな操作をこのように設計する必要があります。
-
使用頻度。これには、将来の事業開発について少し事前の判断が必要です。たとえば、検索アイテムの州と都市は、あいまい検索マッチングの機能を実装する必要があります。
将来どのような事業を行っても、都道府県や市があれば、基本的にこの機能は必須です。
<Select disabled={disabled} allowClear value={value} showArrow={!showSearch} filterOption={false} showSearch={showSearch} placeholder={placeholder} onSearch={showSearch ? onSearch : null} onSelect={onSelect} onClear={onClear} style={{ width }}>
<Select.Option key={item[optionValue]} value={item[optionValue]}>
{item[optionKey]}
</Select.Option>
</Select>;
パラメトリックデザイン
通用组件,差异的部分,一般在功能设计的时候会通过外部传参区分或者控制。所以开发通用组件,参数设计是重要的一个环节。
如果刚开始不是很擅长设计参数,可以参考Antd的参数设计,Antd的组件丰富且功能强大,所以参数考虑的也很周全。边学边练,效果更佳。
如图为Antd的Input输入框组件「平平无奇」的参数:
Antd组件功能赏析
电影有精彩片段赏析,Antd的组件很丰富,如果一一列举,详细介绍,可能我要写到下个月,所以我选了几个常见且基础的组件,来看看Antd是怎么设计这些组件的。
官网指路☞Ant Design
赏析前准备
学习第三方组件之前,不能盲目看代码,可能会找不到重点或者被大量的逻辑绕晕。我一般学习之前先做三方面准备:
- 先明确组件要实现什么功能,比如输入框是否不可操作,是否回显数据等;
- 然后看组件参数,把参数分为控制UI布局、控制内容展示、控制操作功能等几种;比如通过disabled的值控制输入框是否可以操作,通过设置value的值进行数据回显等;
- 最后去思考这些参数怎么实现具体的功能,就比较容易想清楚了。
Grid 栅格
栅格化布局,基于行(row)和列(col)来定义信息区块,可以将区域24等分。通过 row 在水平方向建立一组 column,内容放置于 col 内。
展示层
看col文件中这三行代码,和各种style、className变量。不难发现,栅格化布局主要是通过组件参数对样式的控制来实现的。
return (
<div {...others} style={{ ...mergedStyle, ...style }} className={classes} ref={ref}>
{children}
</div>
);
布局设计
结合参数说明和代码分析,可以大致总结出栅格布局的设计如下:
1.栅格组件基于 Flex 布局。
2.栅格的占位格数,也是它的宽度,样式实现时使用百分比,比如span的值为6时,24等分之后,它的百分比是25%。
.ant-col-6 {
display: block;
flex: 0 0 25%;
max-width: 25%;
}
3.区块间隔格数的值实际上是设置的padding值的2倍,是相邻两个模块的间距之和。所以代码中进行了除以2的处理。
if (gutter && gutter[0] > 0) {
const horizontalGutter = gutter[0] / 2;
mergedStyle.paddingLeft = horizontalGutter;
mergedStyle.paddingRight = horizontalGutter;
}
4.响应式布局,支持六个响应尺寸:xs、sm、md、lg、xl、xxl。参数支持多类型可以是number类型,也可以是Object类型。使用typeof判断参数类型。
if (typeof propSize === 'number') {
sizeProps.span = propSize;
} else if (typeof propSize === 'object') {
sizeProps = propSize || {};
}
布局功能分析告一段落,栅格组件赏析也就收工了。
Steps 步骤条
我们来看看步骤条的功能。
- 步骤条状态,已完成、进行中、未开始、运行错误。
- 两种展示方式,横向和纵向。
- 不同展示类型,数值类、自定图标类、点状类。
- 内容展示,标题、子标题、详情描述。
rc-steps
我在看Antd的源码时发现,有些组件底层用的第三方react-component中的组件。当然这个组件库也是属于Antd的。所以想研究Steps组件的功能,需要翻另一个组件库的代码react-componentr/steps。
import RcSteps from 'rc-steps';
步骤条状态
既可以通过status直接指定当前步骤状态,也可以通过对比current和步骤的数值确定步骤的状态。
const stepNumber = initial + index;
if (status === 'error' && index === current - 1) {
childProps.className = `${prefixCls}-next-error`;
}
if (!child.props.status) {
if (stepNumber === current) {
childProps.status = status;
} else if (stepNumber < current) {
childProps.status = 'finish';
} else {
childProps.status = 'wait';
}
}
展示类型
步骤条支持多种不同的展示类型,代码实现上主要是通过条件语句判断。
- 点状类型,支持自定义展示。当点状步骤条参数progressDot的值是函数类型时,会使用传入的值;否则使用内部定义的点状展示内容。
- 自定义图标,参数icon表示步骤图标的类型,当它有值的时候,步骤条会显示成它的值。有两个特殊的图标:成功状态、失败状态,这两个状态的图标如果使用组件时没有进行自定义,会取内部定义的图标。
- 默认类型,放到条件判断最底层,当其他判断条件的参数没有值时,步骤条会展示内部定义的默认类型。
条件判断
内部定义的成功和失败的图标
const icons = {
finish: <CheckOutlined className={`${prefixCls}-finish-icon`} />,
error: <CloseOutlined className={`${prefixCls}-error-icon`} />,
};
Table 表格
Antd的Table表格,功能很强大,单看文档中的使用介绍就能感觉出来,可用功能大概30多种。我带着这些功能是怎样实现的好奇心,研究了Antd的源码。内容有点多,我挑基础的部分讲一讲。
rc-table
Table组件,底层主要使用react-component中的table组件。
columns
- 参数columns表示表格列的配置描述,表格有哪些列表项都是通过它定义的。
- Tabel组件会将columns传入RcTable组件。
- columns的值确定表头thead都有哪些分组。
- tbody中表格项的值,也是通过columns中列表项的dataIndex变量,从参数dataSource中找到对应的值。
{flattenColumns.map((column: ColumnType<RecordType>, colIndex) => {
const { render, dataIndex, className: columnClassName } = column;
return (
<Cell
className={columnClassName}
ellipsis={column.ellipsis}
align={column.align}
component={cellComponent}
prefixCls={prefixCls}
key={key}
record={record}
index={index}
renderIndex={renderIndex}
dataIndex={dataIndex}
render={render}
shouldCellUpdate={column.shouldCellUpdate}
expanded={appendCellNode && expanded}
{...fixedInfo}
appendNode={appendCellNode}
additionalProps={additionalCellProps}
/>
);
})}
dataSource
- Table的参数dataSource实现表格数据回显。
- dataSource传入Tabel组件会根据分页功能处理成pageData对象,传入RcTable组件。
- 在RcTable组件中,表格列展示内容是封装到子组件Body中的。组件Body会先循环渲染表格的行数据,每一行下面包含一个BodyRow子组件
- BodyRow子组件,行数据会进行循环单元格数据,而单元格的内容封装在Cell子组件中。
- Cell单元格组件中,结合columns中的dataIndex确定最终回显的值。
其中单元格的标签会根据传入的component的值不同,使用不同的标签,默认为td,表头thead传入的为tr。
component: Component = 'td',
......
return (
<Component {...componentProps}>
{appendNode}
{mergedChildNode}
</Component>
);
Table组件比较复杂,功能比较丰富,组件的颗粒度也很细,我研究columns和dataSource就花了不少时间,更多的功能,后面再慢慢探索吧。
总结
多看一些优秀的项目源码,可以帮助拓展开发思路,提升技术设计思维。
现在有Antd等优秀的UI组件库,好像是不用重复造轮子了。但是奔着学习的目的,去开发一套UI组件还是可以帮助提升技术的。当然这些都是给初级开发者的建议,大佬们,大佬们的技术能力,我还在努力追赶。
组件系列的分享告一段落,后面想换换思维,学习一下游戏开发。下个系列——「记忆力的小游戏」见咯~
我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿。
轻松一笑
聚餐,来两盘土豆丝,为什么是土豆丝,因为便宜;为什么两盘,因为一盘不够。