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

相关推荐
2分钟速写快排34 分钟前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60611 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角2 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!2 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰3 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi4 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen5 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马5 小时前
AI源码分析:拆解模型底层实现逻辑
前端
IT_陈寒6 小时前
React子组件莫名其妙重渲染?你可能漏了这个Hook
前端·人工智能·后端
乘风gg6 小时前
DeepSeek V4.1 Flash 来了,明天中午 Flash 降价 60%
前端·ai编程·claude