仿照AntDesign,实现一个自定义Tab

一、介绍

Tab 组件是开发中经常使用到的组件,有时直接使用组件自带的组件会比较方便,但是如果有一些自定义需求的时候,这个时候使用Tab组件就会变得比较麻烦,所以,下面我们将仿照Antd Design的Tab组件,实现一个自定义的Tab组件。

  • 目前具备的功能有 主要功能特点:
  1. 支持横向和纵向布局
  2. 支持禁用状态
  3. 支持键盘导航(通过ARIA属性)
  4. 平滑的状态过渡动画
  5. 类型校验(PropTypes)
  6. 响应式设计基础
  7. 支持自定义样式覆盖
  • 与Ant Design的主要差异:
  1. 更简化,没有复杂的状态管理
  2. 更轻量级(无额外依赖)
  3. 基础样式实现,可根据需要扩展
  4. 缺少部分高级功能(如快捷键、动画效果等)

二、具体实现过程

下面是具体的代码合实现过程:

jsx 复制代码
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import './CustomTabs.css';

const Tabs = ({ activeKey, onChange, children, tabPosition }) => {
  const [currentActiveKey, setCurrentActiveKey] = useState(activeKey || '');

  const handleTabClick = (key) => {
    if (key !== currentActiveKey) {
      setCurrentActiveKey(key);
      onChange?.(key);
    }
  };

  const getTabs = () => {
    return React.Children.map(children, (child, index) => {
      if (!React.isValidElement(child)) return null;
      
      const { key, disabled, tab } = child.props;
      const isActive = key === currentActiveKey;

      return (
        <div
          key={key || index}
          role="tab"
          className={`tab ${isActive ? 'active' : ''} ${disabled ? 'disabled' : ''}`}
          onClick={() => !disabled && handleTabClick(key || index)}
          aria-selected={isActive}
          aria-disabled={disabled}
        >
          {tab || child.props.children}
        </div>
      );
    });
  };

  const getContent = () => {
    return React.Children.map(children, (child) => {
      if (!React.isValidElement(child)) return null;
      
      const { key } = child.props;
      const isActive = key === currentActiveKey;

      return (
        <div
          role="tabpanel"
          id={`tabpanel-${key}`}
          aria-labelledby={`tab-${key}`}
          className={`tab-content ${isActive ? 'active' : ''}`}
        >
          {child.props.children}
        </div>
      );
    });
  };

  return (
    <div className={`custom-tabs ${tabPosition}`}>
      <div className="tab-list">
        {getTabs()}
      </div>
      <div className="tab-content-wrapper">
        {getContent()}
      </div>
    </div>
  );
};

const TabPane = ({ tab, children }) => {
  return <>{children}</>;
};

Tabs.TabPane = TabPane;

// PropTypes
Tabs.propTypes = {
  activeKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
  onChange: PropTypes.func,
  children: PropTypes.node.isRequired,
  tabPosition: PropTypes.oneOf(['top', 'bottom', 'left', 'right']),
};

export default Tabs;

配套的CSS样式 (CustomTabs.css):

css 复制代码
.custom-tabs {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}

.tab-list {
  display: flex;
  border-bottom: 1px solid #f0f0f0;
  position: relative;
}

.tab {
  padding: 12px 24px;
  cursor: pointer;
  border-bottom: 3px solid transparent;
  color: #1890ff;
  transition: all 0.3s;
  font-size: 14px;
  position: relative;
}

.tab:hover {
  color: #40a9ff;
}

.tab.active {
  color: #1890ff;
  border-bottom-color: #1890ff;
  font-weight: 500;
}

.tab.disabled {
  color: #ccc;
  cursor: not-allowed;
}

.tab-content-wrapper {
  padding: 24px;
  border: 1px solid #f0f0f0;
  border-top: none;
}

.tab-content {
  display: none;
}

.tab-content.active {
  display: block;
}

/* 垂直布局 */
.custom-tabs.vertical {
  display: flex;
}

.custom-tabs.vertical .tab-list {
  flex-direction: column;
  border-bottom: none;
  border-right: 1px solid #f0f0f0;
}

.custom-tabs.vertical .tab {
  padding: 16px;
  border-bottom: none;
  border-right: 3px solid transparent;
}

.custom-tabs.vertical .tab.active {
  border-right-color: #1890ff;
}

.custom-tabs.vertical .tab-content-wrapper {
  padding: 24px;
  border: 1px solid #f0f0f0;
  border-left: none;
}

使用示例:

jsx 复制代码
import Tabs from './CustomTabs';

function App() {
  return (
    <div>
      <Tabs defaultActiveKey="1" onChange={(key) => console.log(key)}>
        <Tabs.TabPane tab="Tab 1">
          <p>Content of Tab 1</p>
        </Tabs.TabPane>
        <Tabs.TabPane tab="Tab 2" disabled>
          <p>Content of Tab 2</p>
        </Tabs.TabPane>
        <Tabs.TabPane tab="Tab 3">
          <p>Content of Tab 3</p>
        </Tabs.TabPane>
      </Tabs>

      <Tabs tabPosition="bottom">
        <Tabs.TabPane tab="Tab A">
          <p>Bottom tab content</p>
        </Tabs.TabPane>
      </Tabs>
    </div>
  );
}

三、拓展和其它

可以根据实际需求继续扩展以下功能:

• 动态添加/删除tabs

• 禁用状态样式定制

• 懒加载内容

• 动画过渡效果

• 更多位置选项(如卡片式布局)

• 图标支持

• 类型扩展(如card类型)

相关推荐
深蓝AI3 小时前
Meta 开源用了八年的设计系统 astryx:连坐两天 GitHub 总榜第一,组件库开始写给 agent 的说明书
前端·人工智能
IT_陈寒4 小时前
Vue的watch把我坑惨了,原来要这样用才靠谱
前端·人工智能·后端
1-1=04 小时前
Vue 3 模板语
前端·javascript·vue.js
用户7610304473714 小时前
【转战智能眼镜开发】ROKID AIUI 小游戏 亚马逊标枪项目实战讲解
前端
风卿4 小时前
看着AI写代码太孤独了,来欣赏星云吧:一个DSH 装饰插件
前端·javascript
牧艺4 小时前
弹层不用再手算坐标了:CSS Anchor Positioning 从原理到可上线
前端·javascript·css
用户老李5 小时前
YOLO 目标检测自学教程 07:YOLOv3——多尺度预测
前端
moshuying5 小时前
ESP32 墨水屏金价牌(上):从点亮到把图片推过去
前端
李剑一5 小时前
别再抬杠,前端岗已经没了!无论前后端,我们最终都要成“全干”工程师
前端·面试
Highcharts.js5 小时前
Highcharts 仪表盘与速度仪图表库介绍
前端·信息可视化·仪表板·highcharts·图表开发·图表库·仪表可视化