"Technology Sharing" takes Antd as an example to quickly open up the two veins of Ren and Du of UI component development

foreword

I still remember that I was still a rookie entering the workplace. When I went out for an interview, I would always be asked if I would develop components. At that time, the project development used off-the-shelf UI components, initially Element UI, and later replaced by Antd. No matter which component you change, it will help save a lot of development time. In your usual component development, you can at most carry out some simple secondary encapsulation of titles, pop-up windows, and tables. In a word, the "doing" of component development is still shallow, so the confidence is slightly insufficient during the interview.

After years of precipitation and accumulation of experience, it is no longer difficult to develop a set of UI components by yourself. Open the source code of Antd and want to study how the technical team of Antd implements the components we see on the official website.

Let me tell you a story of my childhood and ignorance. When I first started using Element, it was quite confusing. I just wondered if you were hungry. Isn't it a takeaway? Why do you still provide a technical component library? It was only later that I learned that their technical team is also very, very powerful.

"Have you written a generic component?"

The key to this interview question is how to write common components.

System characteristics

Nowadays, the UI component library is rich and mature, so you may think that in daily development, common components will be written rarely, but it is not.

Every system, whether it is business features, interactive features or UI features, can sort out some common components, such as titles, page layouts, lists, editable tables, fuzzy search boxes, etc.

Take a list as an example

Antd has a ready-made Table component, but in our actual development, the general list management page has search items and data display, and may also have a search reset button or a search export button.

Therefore, common components are useful. Once packaged, thousands of list management pages can be completed with one component.

{!resetAble && (
    <Button type='primary' onClick={handleReset}>
      重置
    </Button>
)}
{exportable && (
    <Button type='primary' onClick={handleExport}>
        导出
     </Button>
)}
<Table dataSource={list} columns={columns}/>;

Universal function

It is known that some functions in daily development can indeed be made into general components, so how to define general boundaries?

Too high generality will lead to too complex code, too low generality, and low development efficiency. I generally observe the following two points:

  1. When this function is used, it may not have much to do with the business. UI or interactive operations need to be designed in this way under any business line, such as editable forms.

  2. The frequency of use, which requires a little pre-judgment of future business development. For example, the provinces and cities in the search items need to implement the function of fuzzy search matching.

No matter what kind of business in the future, as long as there are provinces and cities, this function is basically required.

<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>;

Parametric design

通用组件,差异的部分,一般在功能设计的时候会通过外部传参区分或者控制。所以开发通用组件,参数设计是重要的一个环节。

如果刚开始不是很擅长设计参数,可以参考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组件还是可以帮助提升技术的。当然这些都是给初级开发者的建议,大佬们,大佬们的技术能力,我还在努力追赶。

组件系列的分享告一段落,后面想换换思维,学习一下游戏开发。下个系列——「记忆力的小游戏」见咯~

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿

轻松一笑

聚餐,来两盘土豆丝,为什么是土豆丝,因为便宜;为什么两盘,因为一盘不够。

Guess you like

Origin juejin.im/post/7121263887420227621