当前位置: 代码迷 >> 综合 >> Ant-design 源码分析之数据展示(十二)Tabs
  详细解决方案

Ant-design 源码分析之数据展示(十二)Tabs

热度:33   发布时间:2023-11-19 18:17:10.0

Ant-design 源码分析之数据展示(十二)Tabs

2021SC@SDUSC

一、组件结构

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

ant中Tabs的index.tsx中引入了rc-tabs。

二、antd组件调用关系

1、index.tsx

导入相应模块以及相应的ICON图标

import * as React from 'react';
import RcTabs, {
     TabPane, TabsProps as RcTabsProps, TabPaneProps } from 'rc-tabs';
import {
     EditableConfig } from 'rc-tabs/lib/interface';
import classNames from 'classnames';
import EllipsisOutlined from '@ant-design/icons/EllipsisOutlined';
import PlusOutlined from '@ant-design/icons/PlusOutlined';
import CloseOutlined from '@ant-design/icons/CloseOutlined';import devWarning from '../_util/devWarning';
import {
     ConfigContext } from '../config-provider';
import SizeContext, {
     SizeType } from '../config-provider/SizeContext';

声明TabsProps接口

export type TabsType = 'line' | 'card' | 'editable-card';
export type TabsPosition = 'top' | 'right' | 'bottom' | 'left';export {
     TabPaneProps };export interface TabsProps extends Omit<RcTabsProps, 'editable'> {
    type?: TabsType;size?: SizeType;hideAdd?: boolean;centered?: boolean;addIcon?: React.ReactNode;onEdit?: (e: React.MouseEvent | React.KeyboardEvent | string, action: 'add' | 'remove') => void;
}

addIcon:自定义添加按钮,类型为ReactNode
size:大小,提供 large default 和 small 三种大小,类型为string
centered:标签居中展示,类型为boolean
type:页签的基本样式,可选 line、card editable-card 类型,string
hideAdd:是否隐藏加号图标,在 type=“editable-card” 时有效,类型为boolean
onEdit:新增和删除页签的回调,在 type=“editable-card” 时有效,类型为(targetKey, action): void

function Tabs({
     type,className,size: propSize,onEdit,hideAdd,centered,addIcon,...props }: TabsProps) {
    const {
     prefixCls: customizePrefixCls, moreIcon = <EllipsisOutlined /> } = props;const {
     getPrefixCls, direction } = React.useContext(ConfigContext);const prefixCls = getPrefixCls('tabs', customizePrefixCls);
//可编辑类型处理let editable: EditableConfig | undefined;if (type === 'editable-card') {
    editable = {
    onEdit: (editType, {
      key, event }) => {
    onEdit?.(editType === 'add' ? event : key!, editType);},removeIcon: <CloseOutlined />,addIcon: addIcon || <PlusOutlined />,showAdd: hideAdd !== true,};}const rootPrefixCls = getPrefixCls();devWarning(!('onPrevClick' in props) && !('onNextClick' in props),'Tabs','`onPrevClick` and `onNextClick` has been removed. Please use `onTabScroll` instead.',);return (<SizeContext.Consumer>{
    contextSize => {
    const size = propSize !== undefined ? propSize : contextSize;return (<RcTabsdirection={
    direction}moreTransitionName={
    `${
      rootPrefixCls}-slide-up`}{
    ...props}className={
    classNames({
    [`${
      prefixCls}-${
      size}`]: size,[`${
      prefixCls}-card`]: ['card', 'editable-card'].includes(type as string),[`${
      prefixCls}-editable-card`]: type === 'editable-card',[`${
      prefixCls}-centered`]: centered,},className,)}editable={
    editable}moreIcon={
    moreIcon}prefixCls={
    prefixCls}/>);}}</SizeContext.Consumer>);
}Tabs.TabPane = TabPane;export default Tabs;

2、rc-tabs/Tabs.tsx

导入相应模块以及相应的ICON图标

// Accessibility https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/Tab_Role
import * as React from 'react';
import {
     useEffect, useState } from 'react';
import classNames from 'classnames';
import toArray from 'rc-util/lib/Children/toArray';
import isMobile from 'rc-util/lib/isMobile';
import useMergedState from 'rc-util/lib/hooks/useMergedState';
import TabNavList from './TabNavList';
import TabPanelList from './TabPanelList';
import type {
     TabPaneProps } from './TabPanelList/TabPane';
import TabPane from './TabPanelList/TabPane';
import type {
    TabPosition,RenderTabBar,TabsLocale,EditableConfig,AnimatedConfig,OnTabScroll,Tab,TabBarExtraContent,
} from './interface';
import TabContext from './TabContext';

声明TabsProps接口

let uuid = 0;export interface TabsProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
    prefixCls?: string;className?: string;style?: React.CSSProperties;children?: React.ReactNode;id?: string;activeKey?: string;defaultActiveKey?: string;direction?: 'ltr' | 'rtl';animated?: boolean | AnimatedConfig;renderTabBar?: RenderTabBar;tabBarExtraContent?: TabBarExtraContent;tabBarGutter?: number;tabBarStyle?: React.CSSProperties;tabPosition?: TabPosition;destroyInactiveTabPane?: boolean;onChange?: (activeKey: string) => void;onTabClick?: (activeKey: string, e: React.KeyboardEvent | React.MouseEvent) => void;onTabScroll?: OnTabScroll;editable?: EditableConfig;// Accessibilitylocale?: TabsLocale;// IconsmoreIcon?: React.ReactNode;/** @private Internal usage. Not promise will rename in future */moreTransitionName?: string;
}

activeKey:当前激活 tab 面板的 key,类型为string
addIcon:自定义添加按钮,类型为ReactNode
animated:是否使用动画切换 Tabs, 仅生效于 tabPosition=“top”,类型为boolean | { inkBar: boolean, tabPane: boolean }
centered:标签居中展示,类型为boolean
defaultActiveKey:初始化选中面板的 key,如果没有设置 activeKey,类型为string
hideAdd:是否隐藏加号图标,在 type=“editable-card” 时有效,类型为boolean
moreIcon:自定义折叠 icon,类型为ReactNode
renderTabBar:替换 TabBar,用于二次封装标签头,类型为(props: DefaultTabBarProps, DefaultTabBar: React.ComponentClass) => React.ReactElement
size:大小,提供 large default 和 small 三种大小,类型为string
tabBarExtraContent:tab bar 上额外的元素,类型为ReactNode | {left?: ReactNode, right?: ReactNode}
tabBarGutter:tabs 之间的间隙,类型为number
tabBarStyle:tab bar 的样式对象,类型为object
tabPosition:页签位置,可选值有 top right bottom left,类型为string
destroyInactiveTabPane:被隐藏时是否销毁 DOM 结构,类型为boolean
type:页签的基本样式,可选 line、card editable-card 类型,string
onChange:切换面板的回调,类型为function(activeKey) {}
onEdit:新增和删除页签的回调,在 type=“editable-card” 时有效,类型为(targetKey, action): void
onTabClick:tab 被点击的回调,类型为function(key: string, event: MouseEvent)
onTabScroll:tab 滚动时触发,类型为function({ direction: left | right | top | bottom })

function parseTabList(children: React.ReactNode): Tab[] {
    return toArray(children).map((node: React.ReactElement<TabPaneProps>) => {
    if (React.isValidElement(node)) {
    const key = node.key !== undefined ? String(node.key) : undefined;return {
    key,...node.props,node,};}return null;}).filter(tab => tab);
}
function Tabs({
    id,prefixCls = 'rc-tabs',className,children,direction,activeKey,defaultActiveKey,editable,animated = {
    inkBar: true,tabPane: false,},tabPosition = 'top',tabBarGutter,tabBarStyle,tabBarExtraContent,locale,moreIcon,moreTransitionName,destroyInactiveTabPane,renderTabBar,onChange,onTabClick,onTabScroll,...restProps}: TabsProps,ref: React.Ref<HTMLDivElement>,
) {
    const tabs = parseTabList(children);const rtl = direction === 'rtl';let mergedAnimated: AnimatedConfig | false;if (animated === false) {
    mergedAnimated = {
    inkBar: false,tabPane: false,};} else if (animated === true) {
    mergedAnimated = {
    inkBar: true,tabPane: true,};} else {
    mergedAnimated = {
    inkBar: true,tabPane: false,...(typeof animated === 'object' ? animated : {
    }),};}

移动

  // ======================== Mobile ========================const [mobile, setMobile] = useState(false);useEffect(() => {
    // Only update on the client sidesetMobile(isMobile());}, []);
  // ====================== Active Key ======================//初始化const [mergedActiveKey, setMergedActiveKey] = useMergedState<string>(() => tabs[0]?.key, {
    value: activeKey,defaultValue: defaultActiveKey,});const [activeIndex, setActiveIndex] = useState(() =>tabs.findIndex(tab => tab.key === mergedActiveKey),);
  // Reset active key if not exist anymore//如果不存在ActiveKeyuseEffect(() => {
    let newActiveIndex = tabs.findIndex(tab => tab.key === mergedActiveKey);if (newActiveIndex === -1) {
    newActiveIndex = Math.max(0, Math.min(activeIndex, tabs.length - 1));setMergedActiveKey(tabs[newActiveIndex]?.key);}setActiveIndex(newActiveIndex);}, [tabs.map(tab => tab.key).join('_'), mergedActiveKey, activeIndex]);
  // ===================== Accessibility ====================const [mergedId, setMergedId] = useMergedState(null, {
    value: id,});
//如果不在左右,设为顶部let mergedTabPosition = tabPosition;if (mobile && !['left', 'right'].includes(tabPosition)) {
    mergedTabPosition = 'top';}// Async generate id to avoid ssr mapping failed
//异步设置iduseEffect(() => {
    if (!id) {
    setMergedId(`rc-tabs-${
      process.env.NODE_ENV === 'test' ? 'test' : uuid}`);uuid += 1;}}, []);
  // ======================== Events ========================//改变标签页function onInternalTabClick(key: string, e: React.MouseEvent | React.KeyboardEvent) {
    onTabClick?.(key, e);const isActiveChanged = key !== mergedActiveKey;setMergedActiveKey(key);if (isActiveChanged) {
    onChange?.(key);}}
  // ======================== Render ========================const sharedProps = {
    id: mergedId,activeKey: mergedActiveKey,animated: mergedAnimated,tabPosition: mergedTabPosition,rtl,mobile,};let tabNavBar: React.ReactElement;const tabNavBarProps = {
    ...sharedProps,editable,locale,moreIcon,moreTransitionName,tabBarGutter,onTabClick: onInternalTabClick,onTabScroll,extra: tabBarExtraContent,style: tabBarStyle,panes: children,};if (renderTabBar) {
    tabNavBar = renderTabBar(tabNavBarProps, TabNavList);} else {
    tabNavBar = <TabNavList {
    ...tabNavBarProps} />;}return (<TabContext.Provider value={
    {
     tabs, prefixCls }}><divref={
    ref}id={
    id}className={
    classNames(prefixCls,`${
      prefixCls}-${
      mergedTabPosition}`,{
    [`${
      prefixCls}-mobile`]: mobile,[`${
      prefixCls}-editable`]: editable,[`${
      prefixCls}-rtl`]: rtl,},className,)}{
    ...restProps}>{
    tabNavBar}<TabPanelListdestroyInactiveTabPane={
    destroyInactiveTabPane}{
    ...sharedProps}animated={
    mergedAnimated}/></div></TabContext.Provider>);
}const ForwardTabs = React.forwardRef(Tabs);export type ForwardTabsType = typeof ForwardTabs & {
     TabPane: typeof TabPane };(ForwardTabs as ForwardTabsType).TabPane = TabPane;export default ForwardTabs as ForwardTabsType;

3、rc-tabs/TabPanelList/index.tsx
导入相应模块以及相应的ICON图标

import * as React from 'react';
import classNames from 'classnames';
import TabContext from '../TabContext';
import type {
     TabPosition, AnimatedConfig } from '../interface';
export interface TabPanelListProps {
    activeKey: React.Key;id: string;rtl: boolean;animated?: AnimatedConfig;tabPosition?: TabPosition;destroyInactiveTabPane?: boolean;
}
export default function TabPanelList({
     id,activeKey,animated,tabPosition,rtl,destroyInactiveTabPane, }: TabPanelListProps) {
    const {
     prefixCls, tabs } = React.useContext(TabContext);const tabPaneAnimated = animated.tabPane;const activeIndex = tabs.findIndex(tab => tab.key === activeKey);return (<div className={
    classNames(`${
      prefixCls}-content-holder`)}><divclassName={
    classNames(`${
      prefixCls}-content`, `${
      prefixCls}-content-${
      tabPosition}`, {
    [`${
      prefixCls}-content-animated`]: tabPaneAnimated,})}style={
    activeIndex && tabPaneAnimated? {
     [rtl ? 'marginRight' : 'marginLeft']: `-${
      activeIndex}00%` }: null}>{
    tabs.map(tab => {
    return React.cloneElement(tab.node, {
    key: tab.key,prefixCls,tabKey: tab.key,id,animated: tabPaneAnimated,active: tab.key === activeKey,destroyInactiveTabPane,});})}</div></div>);
}

rc-tabs/TabPanelList/TabPane.tsx
导入相应模块以及相应的ICON图标

import * as React from 'react';
import classNames from 'classnames';
export interface TabPaneProps {
    tab?: React.ReactNode;className?: string;style?: React.CSSProperties;disabled?: boolean;children?: React.ReactNode;forceRender?: boolean;closable?: boolean;closeIcon?: React.ReactNode;// Pass by TabPaneListprefixCls?: string;tabKey?: string;id?: string;animated?: boolean;active?: boolean;destroyInactiveTabPane?: boolean;
}

closeIcon:自定义关闭图标,在 type="editable-card"时有效,类型为ReactNode
forceRender:被隐藏时是否渲染 DOM 结构,类型为boolean
key:对应 activeKey,类型为string
tab:选项卡头显示文字,类型为ReactNode

export default function TabPane({
     prefixCls,forceRender,className,style,id,active,animated,destroyInactiveTabPane,tabKey,children, }: TabPaneProps) {
    const [visited, setVisited] = React.useState(forceRender);React.useEffect(() => {
    if (active) {
    setVisited(true);} else if (destroyInactiveTabPane) {
    setVisited(false);}}, [active, destroyInactiveTabPane]);const mergedStyle: React.CSSProperties = {
    };if (!active) {
    if (animated) {
    mergedStyle.visibility = 'hidden';mergedStyle.height = 0;mergedStyle.overflowY = 'hidden';} else {
    mergedStyle.display = 'none';}}return (<divid={
    id && `${
      id}-panel-${
      tabKey}`}role="tabpanel"tabIndex={
    active ? 0 : -1}aria-labelledby={
    id && `${
      id}-tab-${
      tabKey}`}aria-hidden={
    !active}style={
    {
     ...mergedStyle, ...style }}className={
    classNames(`${
      prefixCls}-tabpane`,active && `${
      prefixCls}-tabpane-active`,className,)}>{
    (active || visited || forceRender) && children}</div>);
}
  相关解决方案