一、介绍
Tab 组件是开发中经常使用到的组件,有时直接使用组件自带的组件会比较方便,但是如果有一些自定义需求的时候,这个时候使用Tab组件就会变得比较麻烦,所以,下面我们将仿照Antd Design的Tab组件,实现一个自定义的Tab组件。
- 目前具备的功能有 主要功能特点:
- 支持横向和纵向布局
- 支持禁用状态
- 支持键盘导航(通过ARIA属性)
- 平滑的状态过渡动画
- 类型校验(PropTypes)
- 响应式设计基础
- 支持自定义样式覆盖
- 与Ant Design的主要差异:
- 更简化,没有复杂的状态管理
- 更轻量级(无额外依赖)
- 基础样式实现,可根据需要扩展
- 缺少部分高级功能(如快捷键、动画效果等)
二、具体实现过程
下面是具体的代码合实现过程:
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类型)