仿照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类型)

相关推荐
熊猫比分管理员6 分钟前
【全栈源码解决方案】Vue+Java四端齐全,一周交付可运行项目!
java·前端·vue.js
o***74178 分钟前
【Nginx 】Nginx 部署前端 vue 项目
前端·vue.js·nginx
坚持就完事了8 分钟前
CSS-5:盒子模型
前端·css·html
一 乐8 分钟前
考公|考务考试|基于SprinBoot+vue的考公在线考试系统(源码+数据库+文档)
前端·javascript·数据库·vue.js·spring boot·课程设计
吃饺子不吃馅9 分钟前
优化:如何避免 React Context 引起的全局挂载节点树重新渲染
前端·面试·github
q***721910 分钟前
Y20030018基于Java+Springboot+mysql+jsp+layui的家政服务系统的设计与实现 源代码 文档
android·前端·后端
林太白33 分钟前
跟着TRAE SOLO全链路看看项目部署服务器全流程吧
前端·javascript·后端
humor40 分钟前
Quill 2.x 从 0 到 1 实战 - 为 AI+Quill 深度结合铺路
前端·vue.js
FinClip1 小时前
京东外卖App独立上线,超级App如何集成海量小程序?
前端
一颗苹果OMG1 小时前
随着AI的发展,测试跟prompt会不会成为每个程序员的必修课
前端·程序员·全栈