Ant-design 源码分析之数据展示(四)Card

Ant-design 源码分析之数据展示(四)Card

2021SC@SDUSC

一、组件结构

1、ant代码结构
在这里插入图片描述
2、组件结构

ant中Card的index.tsx中引入了Meta和Grid。

二、antd组件调用关系

1、index.tsx
导入相应模块以及相应的ICON图标

import * as React from 'react';
import classNames from 'classnames';
import omit from 'rc-util/lib/omit';
import Grid from './Grid';
import Meta from './Meta';
import Tabs, { TabsProps } from '../tabs';
import Row from '../row';
import Col from '../col';
import { ConfigContext } from '../config-provider';
import SizeContext from '../config-provider/SizeContext';

函数,用于触发事件

function getAction(actions: React.ReactNode[]) {
  const actionList = actions.map((action, index) => (
    // eslint-disable-next-line react/no-array-index-key
    <li style={{ width: `${100 / actions.length}%` }} key={`action-${index}`}>
      <span>{action}</span>
    </li>
  ));
  return actionList;
}

export { CardGridProps } from './Grid';
export { CardMetaProps } from './Meta';

export type CardType = 'inner';
export type CardSize = 'default' | 'small';

卡片菜单栏

export interface CardTabListType {
  key: string;
  tab: React.ReactNode;
  disabled?: boolean;
}

定义CardProps接口

export interface CardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'> {
  prefixCls?: string;
  title?: React.ReactNode;
  extra?: React.ReactNode;
  bordered?: boolean;
  headStyle?: React.CSSProperties;
  bodyStyle?: React.CSSProperties;
  style?: React.CSSProperties;
  loading?: boolean;
  hoverable?: boolean;
  children?: React.ReactNode;
  id?: string;
  className?: string;
  size?: CardSize;
  type?: CardType;
  cover?: React.ReactNode;
  actions?: React.ReactNode[];
  tabList?: CardTabListType[];
  tabBarExtraContent?: React.ReactNode | null;
  onTabChange?: (key: string) => void;
  activeTabKey?: string;
  defaultActiveTabKey?: string;
  tabProps?: TabsProps;
}

actions:卡片操作组,位置在卡片底部,类型为Array
activeTabKey:当前激活页签的 key,类型为string
bodyStyle:内容区域自定义样式,类型为CSSProperties
bordered:是否有边框,类型为boolean
cover:卡片封面,类型为ReactNode
defaultActiveTabKey 初始化选中页签的 key,如果没有设置 activeTabKey string 第一个页签
extra 卡片右上角的操作区域 ReactNode -
headStyle 自定义标题区域样式 CSSProperties -
hoverable:鼠标移过时可浮起,类型为boolean
loading:当卡片内容还在加载中时,可以用 loading 展示一个占位,类型为boolean
size:card 的尺寸,类型为default | small
tabBarExtraContent:tab bar 上额外的元素,类型为ReactNode
tabList:页签标题列表类型为Array<{key: string, tab: ReactNode}>
title:卡片标题,类型为ReactNode
type:卡片类型,可设置为 inner 或 不设置,类型为string
onTabChange:页签切换的回调,类型为(key) => void

定义CardInterface接口

export interface CardInterface extends React.FC<CardProps> {
  Grid: typeof Grid;
  Meta: typeof Meta;
}

实例化接口

const Card: CardInterface = props => {
  const { getPrefixCls, direction } = React.useContext(ConfigContext);
  const size = React.useContext(SizeContext);
//菜单改变
  const onTabChange = (key: string) => {
    props.onTabChange?.(key);
  };
//是否是栅格状卡片
  const isContainGrid = () => {
    let containGrid;
    React.Children.forEach(props.children, (element: JSX.Element) => {
      if (element && element.type && element.type === Grid) {
        containGrid = true;
      }
    });
    return containGrid;
  };

  const {
    prefixCls: customizePrefixCls,
    className,
    extra,
    headStyle = {},
    bodyStyle = {},
    title,
    loading,
    bordered = true,
    size: customizeSize,
    type,
    cover,
    actions,
    tabList,
    children,
    activeTabKey,
    defaultActiveTabKey,
    tabBarExtraContent,
    hoverable,
    tabProps = {},
    ...others
  } = props;

确定卡片样式

  const prefixCls = getPrefixCls('card', customizePrefixCls);

  const loadingBlockStyle =
    bodyStyle.padding === 0 || bodyStyle.padding === '0px' ? { padding: 24 } : undefined;

  const block = <div className={`${prefixCls}-loading-block`} />;

确定卡片大小

  const loadingBlock = (
    <div className={`${prefixCls}-loading-content`} style={loadingBlockStyle}>
      <Row gutter={8}>
        <Col span={22}>{block}</Col>
      </Row>
      <Row gutter={8}>
        <Col span={8}>{block}</Col>
        <Col span={15}>{block}</Col>
      </Row>
      <Row gutter={8}>
        <Col span={6}>{block}</Col>
        <Col span={18}>{block}</Col>
      </Row>
      <Row gutter={8}>
        <Col span={13}>{block}</Col>
        <Col span={9}>{block}</Col>
      </Row>
      <Row gutter={8}>
        <Col span={4}>{block}</Col>
        <Col span={3}>{block}</Col>
        <Col span={16}>{block}</Col>
      </Row>
    </div>
  );

卡片是否可用

  const hasActiveTabKey = activeTabKey !== undefined;
  const extraProps = {
    ...tabProps,
    [hasActiveTabKey ? 'activeKey' : 'defaultActiveKey']: hasActiveTabKey
      ? activeTabKey
      : defaultActiveTabKey,
    tabBarExtraContent,
  };

  let head: React.ReactNode;
  const tabs =
    tabList && tabList.length ? (
      <Tabs
        size="large"
        {...extraProps}
        className={`${prefixCls}-head-tabs`}
        onChange={onTabChange}
      >
        {tabList.map(item => (
          <Tabs.TabPane tab={item.tab} disabled={item.disabled} key={item.key} />
        ))}
      </Tabs>
    ) : null;
    //确定head
  if (title || extra || tabs) {
    head = (
      <div className={`${prefixCls}-head`} style={headStyle}>
        <div className={`${prefixCls}-head-wrapper`}>
          {title && <div className={`${prefixCls}-head-title`}>{title}</div>}
          {extra && <div className={`${prefixCls}-extra`}>{extra}</div>}
        </div>
        {tabs}
      </div>
    );
  }

卡片封面设置

  const coverDom = cover ? <div className={`${prefixCls}-cover`}>{cover}</div> : null;
  const body = (
    <div className={`${prefixCls}-body`} style={bodyStyle}>
      {loading ? loadingBlock : children}
    </div>
  );
  const actionDom =
    actions && actions.length ? (
      <ul className={`${prefixCls}-actions`}>{getAction(actions)}</ul>
    ) : null;
  const divProps = omit(others, ['onTabChange']);
  const mergedSize = customizeSize || size;
  const classString = classNames(
    prefixCls,
    {
      [`${prefixCls}-loading`]: loading,
      [`${prefixCls}-bordered`]: bordered,
      [`${prefixCls}-hoverable`]: hoverable,
      [`${prefixCls}-contain-grid`]: isContainGrid(),
      [`${prefixCls}-contain-tabs`]: tabList && tabList.length,
      [`${prefixCls}-${mergedSize}`]: mergedSize,
      [`${prefixCls}-type-${type}`]: !!type,
      [`${prefixCls}-rtl`]: direction === 'rtl',
    },
    className,
  );

  return (
    <div {...divProps} className={classString}>
      {head}
      {coverDom}
      {body}
      {actionDom}
    </div>
  );

2、Grid.tsx
导入相应模块

import * as React from 'react';
import classNames from 'classnames';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';

声明CardGridProps接口

export interface CardGridProps {
  prefixCls?: string;
  className?: string;
  hoverable?: boolean;
  style?: React.CSSProperties;
}

className:网格容器类名,类型为string
hoverable:鼠标移过时可浮起,类型为boolean
style:定义网格容器类名的样式,类型为CSSProperties

const Grid: React.FC<CardGridProps> = ({ prefixCls, className, hoverable = true, ...props }) => (
  <ConfigConsumer>
    {({ getPrefixCls }: ConfigConsumerProps) => {
      const prefix = getPrefixCls('card', prefixCls);
      const classString = classNames(`${prefix}-grid`, className, {
        [`${prefix}-grid-hoverable`]: hoverable,
      });

      return <div {...props} className={classString} />;
    }}
  </ConfigConsumer>
);

3、Meta.tsx
导入相应模块

import * as React from 'react';
import classNames from 'classnames';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';

定义CardMetaProps接口

export interface CardMetaProps {
  prefixCls?: string;
  style?: React.CSSProperties;
  className?: string;
  avatar?: React.ReactNode;
  title?: React.ReactNode;
  description?: React.ReactNode;
}

avatar:头像/图标,类型为ReactNode
className:容器类名,类型为string
description:描述内容,类型为ReactNode
style:定义容器类名的样式,类型为CSSProperties
title:标题内容,类型为ReactNode

const Meta: React.FC<CardMetaProps> = props => (
  <ConfigConsumer>
    {({ getPrefixCls }: ConfigConsumerProps) => {
      const {
        prefixCls: customizePrefixCls,
        className,
        avatar,
        title,
        description,
        ...others
      } = props;
      const prefixCls = getPrefixCls('card', customizePrefixCls);
      const classString = classNames(`${prefixCls}-meta`, className);
      const avatarDom = avatar ? <div className={`${prefixCls}-meta-avatar`}>{avatar}</div> : null;
      const titleDom = title ? <div className={`${prefixCls}-meta-title`}>{title}</div> : null;
      const descriptionDom = description ? (
        <div className={`${prefixCls}-meta-description`}>{description}</div>
      ) : null;
      const MetaDetail =
        titleDom || descriptionDom ? (
          <div className={`${prefixCls}-meta-detail`}>
            {titleDom}
            {descriptionDom}
          </div>
        ) : null;
      return (
        <div {...others} className={classString}>
          {avatarDom}
          {MetaDetail}
        </div>
      );
    }}
  </ConfigConsumer>
);
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值