Ant-design 源码分析之数据展示(八)Descriptions
2021SC@SDUSC
一、组件结构
1、ant代码结构
![在这里插入图片描述](https://img-blog.csdnimg.cn/018381ed92e74a8a82613af94488c467.png
2、组件结构
ant中Descriptions的index.tsx中引入了Item和Row。
Row引用了Cell
二、antd组件调用关系
1、index.tsx
导入相应模块以及相应的ICON图标
import * as React from 'react';
import classNames from 'classnames';
import toArray from 'rc-util/lib/Children/toArray';
import ResponsiveObserve, {
Breakpoint,
ScreenMap,
responsiveArray,
} from '../_util/responsiveObserve';
import devWarning from '../_util/devWarning';
import { ConfigContext } from '../config-provider';
import Row from './Row';
import DescriptionsItem from './Item';
import { cloneElement } from '../_util/reactNode';
声明DescriptionsContextProps接口
export interface DescriptionsContextProps {
labelStyle?: React.CSSProperties;
contentStyle?: React.CSSProperties;
}
export const DescriptionsContext = React.createContext<DescriptionsContextProps>({});
const DEFAULT_COLUMN_MAP: Record<Breakpoint, number> = {
xxl: 3,
xl: 3,
lg: 3,
md: 3,
sm: 2,
xs: 1,
};
获得按列排列的描述
function getColumn(column: DescriptionsProps['column'], screens: ScreenMap): number {
if (typeof column === 'number') {
return column;
}
if (typeof column === 'object') {
for (let i = 0; i < responsiveArray.length; i++) {
const breakpoint: Breakpoint = responsiveArray[i];
if (screens[breakpoint] && column[breakpoint] !== undefined) {
return column[breakpoint] || DEFAULT_COLUMN_MAP[breakpoint];
}
}
}
return 3;
}
将该描述用子类描述填充
function getFilledItem(
node: React.ReactElement,
span: number | undefined,
rowRestCol: number,
): React.ReactElement {
let clone = node;
if (span === undefined || span > rowRestCol) {
clone = cloneElement(node, {
span: rowRestCol,
});
devWarning(
span === undefined,
'Descriptions',
'Sum of column `span` in a line not match `column` of Descriptions.',
);
}
return clone;
}
获得有子类描述按行排列的描述
function getRows(children: React.ReactNode, column: number) {
const childNodes = toArray(children).filter(n => n);
const rows: React.ReactElement[][] = [];
let tmpRow: React.ReactElement[] = [];
let rowRestCol = column;
childNodes.forEach((node, index) => {
const span: number | undefined = node.props?.span;
const mergedSpan = span || 1;
// Additional handle last one
if (index === childNodes.length - 1) {
tmpRow.push(getFilledItem(node, span, rowRestCol));
rows.push(tmpRow);
return;
}
if (mergedSpan < rowRestCol) {
rowRestCol -= mergedSpan;
tmpRow.push(node);
} else {
tmpRow.push(getFilledItem(node, mergedSpan, rowRestCol));
rows.push(tmpRow);
rowRestCol = column;
tmpRow = [];
}
});
return rows;
}
声明DescriptionsProps接口
export interface DescriptionsProps {
prefixCls?: string;
className?: string;
style?: React.CSSProperties;
bordered?: boolean;
size?: 'middle' | 'small' | 'default';
children?: React.ReactNode;
title?: React.ReactNode;
extra?: React.ReactNode;
column?: number | Partial<Record<Breakpoint, number>>;
layout?: 'horizontal' | 'vertical';
colon?: boolean;
labelStyle?: React.CSSProperties;
contentStyle?: React.CSSProperties;
}
bordered:是否展示边框。类型为boolean
colon:配置 Descriptions.Item 的 colon 的默认值,类型为boolean
column:一行的 DescriptionItems 数量,可以写成像素值或支持响应式的对象写法 { xs: 8, sm: 16, md: 24},类型为number
contentStyle:自定义内容样式,类型为CSSProperties
extra:描述列表的操作区域,显示在右上方,类型为ReactNode
labelStyle:自定义标签样式,类型为CSSProperties
layout:描述布局,类型为horizontal | vertical
size:设置列表的大小。可以设置为 middle 、small, 或不填(只有设置 bordered={true} 生效),类型为default | middle | small
title:描述列表的标题,显示在最顶部,类型为ReactNode
function Descriptions({
prefixCls: customizePrefixCls,
title,
extra,
column = DEFAULT_COLUMN_MAP,
colon = true,
bordered,
layout,
children,
className,
style,
size,
labelStyle,
contentStyle,
}: DescriptionsProps) {
const { getPrefixCls, direction } = React.useContext(ConfigContext);
const prefixCls = getPrefixCls('descriptions', customizePrefixCls);
const [screens, setScreens] = React.useState<ScreenMap>({});
const mergedColumn = getColumn(column, screens);
响应
// Responsive
React.useEffect(() => {
const token = ResponsiveObserve.subscribe(newScreens => {
if (typeof column !== 'object') {
return;
}
setScreens(newScreens);
});
return () => {
ResponsiveObserve.unsubscribe(token);
};
}, []);
子类的嵌套
// Children
const rows = getRows(children, mergedColumn);
//语法
return (
<DescriptionsContext.Provider value={{ labelStyle, contentStyle }}>
<div
className={classNames(
prefixCls,
{
[`${prefixCls}-${size}`]: size && size !== 'default',
[`${prefixCls}-bordered`]: !!bordered,
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
)}
style={style}
>
{(title || extra) && (
<div className={`${prefixCls}-header`}>
{title && <div className={`${prefixCls}-title`}>{title}</div>}
{extra && <div className={`${prefixCls}-extra`}>{extra}</div>}
</div>
)}
<div className={`${prefixCls}-view`}>
<table>
<tbody>
{rows.map((row, index) => (
<Row
key={index}
index={index}
colon={colon}
prefixCls={prefixCls}
vertical={layout === 'vertical'}
bordered={bordered}
row={row}
/>
))}
</tbody>
</table>
</div>
</div>
</DescriptionsContext.Provider>
);
}
2、Row.tsx
导入相应模块以及相应的ICON图标
mport * as React from 'react';
import { DescriptionsItemProps } from './Item';
import Cell from './Cell';
import { DescriptionsContext, DescriptionsContextProps } from '.';
声明CellConfig接口
interface CellConfig {
component: string | [string, string];
type: string;
showLabel?: boolean;
showContent?: boolean;
}
function renderCells(
items: React.ReactElement<DescriptionsItemProps>[],
{ colon, prefixCls, bordered }: RowProps,
{
component,
type,
showLabel,
showContent,
labelStyle: rootLabelStyle,
contentStyle: rootContentStyle,
}: CellConfig & DescriptionsContextProps,
) {
return items.map(
(
{
props: {
label,
children,
prefixCls: itemPrefixCls = prefixCls,
className,
style,
labelStyle,
contentStyle,
span = 1,
},
key,
},
index,
) => {
if (typeof component === 'string') {
return (
<Cell
key={`${type}-${key || index}`}
className={className}
style={style}
labelStyle={{ ...rootLabelStyle, ...labelStyle }}
contentStyle={{ ...rootContentStyle, ...contentStyle }}
span={span}
colon={colon}
component={component}
itemPrefixCls={itemPrefixCls}
bordered={bordered}
label={showLabel ? label : null}
content={showContent ? children : null}
/>
);
}
return [
<Cell
key={`label-${key || index}`}
className={className}
style={{ ...rootLabelStyle, ...style, ...labelStyle }}
span={1}
colon={colon}
component={component[0]}
itemPrefixCls={itemPrefixCls}
bordered={bordered}
label={label}
/>,
<Cell
key={`content-${key || index}`}
className={className}
style={{ ...rootContentStyle, ...style, ...contentStyle }}
span={span * 2 - 1}
component={component[1]}
itemPrefixCls={itemPrefixCls}
bordered={bordered}
content={children}
/>,
];
},
);
}
声明RowProps接口
export interface RowProps {
prefixCls: string;
vertical: boolean;
row: React.ReactElement<DescriptionsItemProps>[];
bordered?: boolean;
colon: boolean;
index: number;
}
const Row: React.FC<RowProps> = props => {
const descContext = React.useContext(DescriptionsContext);
const { prefixCls, vertical, row, index, bordered } = props;
if (vertical) {
return (
<>
<tr key={`label-${index}`} className={`${prefixCls}-row`}>
{renderCells(row, props, {
component: 'th',
type: 'label',
showLabel: true,
...descContext,
})}
</tr>
<tr key={`content-${index}`} className={`${prefixCls}-row`}>
{renderCells(row, props, {
component: 'td',
type: 'content',
showContent: true,
...descContext,
})}
</tr>
</>
);
}
return (
<tr key={index} className={`${prefixCls}-row`}>
{renderCells(row, props, {
component: bordered ? ['th', 'td'] : 'td',
type: 'item',
showLabel: true,
showContent: true,
...descContext,
})}
</tr>
);
};
3、Cell.tsx
导入相应模块以及相应的ICON图标
import * as React from 'react';
import classNames from 'classnames';
判定不为空
function notEmpty(val: any) {
return val !== undefined && val !== null;
}
声明CellProps接口
export interface CellProps {
itemPrefixCls: string;
span: number;
className?: string;
component: string;
style?: React.CSSProperties;
labelStyle?: React.CSSProperties;
contentStyle?: React.CSSProperties;
bordered?: boolean;
label?: React.ReactNode;
content?: React.ReactNode;
colon?: boolean;
}
const Cell: React.FC<CellProps> = ({
itemPrefixCls,
component,
span,
className,
style,
labelStyle,
contentStyle,
bordered,
label,
content,
colon,
}) => {
const Component = component as any;
if (bordered) {
return (
<Component
className={classNames(
{
[`${itemPrefixCls}-item-label`]: notEmpty(label),
[`${itemPrefixCls}-item-content`]: notEmpty(content),
},
className,
)}
style={style}
colSpan={span}
>
{notEmpty(label) && <span style={labelStyle}>{label}</span>}
{notEmpty(content) && <span style={contentStyle}>{content}</span>}
</Component>
);
}
return (
<Component
className={classNames(`${itemPrefixCls}-item`, className)}
style={style}
colSpan={span}
>
<div className={`${itemPrefixCls}-item-container`}>
{label && (
<span
className={classNames(`${itemPrefixCls}-item-label`, {
[`${itemPrefixCls}-item-no-colon`]: !colon,
})}
style={labelStyle}
>
{label}
</span>
)}
{content && (
<span className={classNames(`${itemPrefixCls}-item-content`)} style={contentStyle}>
{content}
</span>
)}
</div>
</Component>
);
};
export default Cell;