Tabs标签页

选项卡切换组件。

何时使用

提供平级的区域将大块内容进行收纳和展现,保持界面整洁。

Ant Design 依次提供了三级选项卡,分别用于不同的场景。

  • 卡片式的页签,提供可关闭的样式,常用于容器顶部。

  • 既可用于容器顶部,也可用于容器内部,是最通用的 Tabs。

  • RadioButton 可作为更次级的页签来使用。

代码演示

Tabs标签页 - 图1

基本

默认选中第一项。

  1. import { Tabs } from 'antd';
  2. const { TabPane } = Tabs;
  3. function callback(key) {
  4. console.log(key);
  5. }
  6. ReactDOM.render(
  7. <Tabs defaultActiveKey="1" onChange={callback}>
  8. <TabPane tab="Tab 1" key="1">
  9. Content of Tab Pane 1
  10. </TabPane>
  11. <TabPane tab="Tab 2" key="2">
  12. Content of Tab Pane 2
  13. </TabPane>
  14. <TabPane tab="Tab 3" key="3">
  15. Content of Tab Pane 3
  16. </TabPane>
  17. </Tabs>,
  18. mountNode,
  19. );

Tabs标签页 - 图2

禁用

禁用某一项。

  1. import { Tabs } from 'antd';
  2. const { TabPane } = Tabs;
  3. ReactDOM.render(
  4. <Tabs defaultActiveKey="1">
  5. <TabPane tab="Tab 1" key="1">
  6. Tab 1
  7. </TabPane>
  8. <TabPane tab="Tab 2" disabled key="2">
  9. Tab 2
  10. </TabPane>
  11. <TabPane tab="Tab 3" key="3">
  12. Tab 3
  13. </TabPane>
  14. </Tabs>,
  15. mountNode,
  16. );

Tabs标签页 - 图3

图标

有图标的标签。

  1. import { Tabs } from 'antd';
  2. import { AppleOutlined, AndroidOutlined } from '@ant-design/icons';
  3. const { TabPane } = Tabs;
  4. ReactDOM.render(
  5. <Tabs defaultActiveKey="2">
  6. <TabPane
  7. tab={
  8. <span>
  9. <AppleOutlined />
  10. Tab 1
  11. </span>
  12. }
  13. key="1"
  14. >
  15. Tab 1
  16. </TabPane>
  17. <TabPane
  18. tab={
  19. <span>
  20. <AndroidOutlined />
  21. Tab 2
  22. </span>
  23. }
  24. key="2"
  25. >
  26. Tab 2
  27. </TabPane>
  28. </Tabs>,
  29. mountNode,
  30. );

Tabs标签页 - 图4

滑动

可以左右、上下滑动,容纳更多标签。

  1. import { Tabs, Radio } from 'antd';
  2. const { TabPane } = Tabs;
  3. class SlidingTabsDemo extends React.Component {
  4. constructor(props) {
  5. super(props);
  6. this.state = {
  7. mode: 'top',
  8. };
  9. }
  10. handleModeChange = e => {
  11. const mode = e.target.value;
  12. this.setState({ mode });
  13. };
  14. render() {
  15. const { mode } = this.state;
  16. return (
  17. <div>
  18. <Radio.Group onChange={this.handleModeChange} value={mode} style={{ marginBottom: 8 }}>
  19. <Radio.Button value="top">Horizontal</Radio.Button>
  20. <Radio.Button value="left">Vertical</Radio.Button>
  21. </Radio.Group>
  22. <Tabs defaultActiveKey="1" tabPosition={mode} style={{ height: 220 }}>
  23. {[...Array(30).keys()].map(i => (
  24. <TabPane tab={`Tab-${i}`} key={i}>
  25. Content of tab {i}
  26. </TabPane>
  27. ))}
  28. </Tabs>
  29. </div>
  30. );
  31. }
  32. }
  33. ReactDOM.render(<SlidingTabsDemo />, mountNode);

Tabs标签页 - 图5

附加内容

可以在页签右边添加附加操作。

  1. import { Tabs, Button } from 'antd';
  2. const { TabPane } = Tabs;
  3. const operations = <Button>Extra Action</Button>;
  4. ReactDOM.render(
  5. <Tabs tabBarExtraContent={operations}>
  6. <TabPane tab="Tab 1" key="1">
  7. Content of tab 1
  8. </TabPane>
  9. <TabPane tab="Tab 2" key="2">
  10. Content of tab 2
  11. </TabPane>
  12. <TabPane tab="Tab 3" key="3">
  13. Content of tab 3
  14. </TabPane>
  15. </Tabs>,
  16. mountNode,
  17. );

Tabs标签页 - 图6

大小

大号页签用在页头区域,小号用在弹出框等较狭窄的容器内。

  1. import { Tabs, Radio } from 'antd';
  2. const { TabPane } = Tabs;
  3. class Demo extends React.Component {
  4. state = { size: 'small' };
  5. onChange = e => {
  6. this.setState({ size: e.target.value });
  7. };
  8. render() {
  9. const { size } = this.state;
  10. return (
  11. <div>
  12. <Radio.Group value={size} onChange={this.onChange} style={{ marginBottom: 16 }}>
  13. <Radio.Button value="small">Small</Radio.Button>
  14. <Radio.Button value="default">Default</Radio.Button>
  15. <Radio.Button value="large">Large</Radio.Button>
  16. </Radio.Group>
  17. <Tabs defaultActiveKey="1" size={size}>
  18. <TabPane tab="Tab 1" key="1">
  19. Content of tab 1
  20. </TabPane>
  21. <TabPane tab="Tab 2" key="2">
  22. Content of tab 2
  23. </TabPane>
  24. <TabPane tab="Tab 3" key="3">
  25. Content of tab 3
  26. </TabPane>
  27. </Tabs>
  28. </div>
  29. );
  30. }
  31. }
  32. ReactDOM.render(<Demo />, mountNode);

Tabs标签页 - 图7

位置

有四个位置,tabPosition="left|right|top|bottom"

  1. import { Tabs, Select } from 'antd';
  2. const { TabPane } = Tabs;
  3. const { Option } = Select;
  4. class Demo extends React.Component {
  5. state = {
  6. tabPosition: 'top',
  7. };
  8. changeTabPosition = tabPosition => {
  9. this.setState({ tabPosition });
  10. };
  11. render() {
  12. return (
  13. <div>
  14. <div style={{ marginBottom: 16 }}>
  15. Tab position
  16. <Select
  17. value={this.state.tabPosition}
  18. onChange={this.changeTabPosition}
  19. dropdownMatchSelectWidth={false}
  20. >
  21. <Option value="top">top</Option>
  22. <Option value="bottom">bottom</Option>
  23. <Option value="left">left</Option>
  24. <Option value="right">right</Option>
  25. </Select>
  26. </div>
  27. <Tabs tabPosition={this.state.tabPosition}>
  28. <TabPane tab="Tab 1" key="1">
  29. Content of Tab 1
  30. </TabPane>
  31. <TabPane tab="Tab 2" key="2">
  32. Content of Tab 2
  33. </TabPane>
  34. <TabPane tab="Tab 3" key="3">
  35. Content of Tab 3
  36. </TabPane>
  37. </Tabs>
  38. </div>
  39. );
  40. }
  41. }
  42. ReactDOM.render(<Demo />, mountNode);

Tabs标签页 - 图8

卡片式页签

另一种样式的页签,不提供对应的垂直样式。

  1. import { Tabs } from 'antd';
  2. const { TabPane } = Tabs;
  3. function callback(key) {
  4. console.log(key);
  5. }
  6. ReactDOM.render(
  7. <Tabs onChange={callback} type="card">
  8. <TabPane tab="Tab 1" key="1">
  9. Content of Tab Pane 1
  10. </TabPane>
  11. <TabPane tab="Tab 2" key="2">
  12. Content of Tab Pane 2
  13. </TabPane>
  14. <TabPane tab="Tab 3" key="3">
  15. Content of Tab Pane 3
  16. </TabPane>
  17. </Tabs>,
  18. mountNode,
  19. );

Tabs标签页 - 图9

新增和关闭页签

只有卡片样式的页签支持新增和关闭选项。使用 closable={false} 禁止关闭。

  1. import { Tabs } from 'antd';
  2. const { TabPane } = Tabs;
  3. class Demo extends React.Component {
  4. constructor(props) {
  5. super(props);
  6. this.newTabIndex = 0;
  7. const panes = [
  8. { title: 'Tab 1', content: 'Content of Tab 1', key: '1' },
  9. { title: 'Tab 2', content: 'Content of Tab 2', key: '2' },
  10. {
  11. title: 'Tab 3',
  12. content: 'Content of Tab 3',
  13. key: '3',
  14. closable: false,
  15. },
  16. ];
  17. this.state = {
  18. activeKey: panes[0].key,
  19. panes,
  20. };
  21. }
  22. onChange = activeKey => {
  23. this.setState({ activeKey });
  24. };
  25. onEdit = (targetKey, action) => {
  26. this[action](targetKey);
  27. };
  28. add = () => {
  29. const { panes } = this.state;
  30. const activeKey = `newTab${this.newTabIndex++}`;
  31. panes.push({ title: 'New Tab', content: 'Content of new Tab', key: activeKey });
  32. this.setState({ panes, activeKey });
  33. };
  34. remove = targetKey => {
  35. let { activeKey } = this.state;
  36. let lastIndex;
  37. this.state.panes.forEach((pane, i) => {
  38. if (pane.key === targetKey) {
  39. lastIndex = i - 1;
  40. }
  41. });
  42. const panes = this.state.panes.filter(pane => pane.key !== targetKey);
  43. if (panes.length && activeKey === targetKey) {
  44. if (lastIndex >= 0) {
  45. activeKey = panes[lastIndex].key;
  46. } else {
  47. activeKey = panes[0].key;
  48. }
  49. }
  50. this.setState({ panes, activeKey });
  51. };
  52. render() {
  53. return (
  54. <Tabs
  55. onChange={this.onChange}
  56. activeKey={this.state.activeKey}
  57. type="editable-card"
  58. onEdit={this.onEdit}
  59. >
  60. {this.state.panes.map(pane => (
  61. <TabPane tab={pane.title} key={pane.key} closable={pane.closable}>
  62. {pane.content}
  63. </TabPane>
  64. ))}
  65. </Tabs>
  66. );
  67. }
  68. }
  69. ReactDOM.render(<Demo />, mountNode);

Tabs标签页 - 图10

卡片式页签容器

用于容器顶部,需要一点额外的样式覆盖。

  1. import { Tabs } from 'antd';
  2. const { TabPane } = Tabs;
  3. ReactDOM.render(
  4. <div className="card-container">
  5. <Tabs type="card">
  6. <TabPane tab="Tab Title 1" key="1">
  7. <p>Content of Tab Pane 1</p>
  8. <p>Content of Tab Pane 1</p>
  9. <p>Content of Tab Pane 1</p>
  10. </TabPane>
  11. <TabPane tab="Tab Title 2" key="2">
  12. <p>Content of Tab Pane 2</p>
  13. <p>Content of Tab Pane 2</p>
  14. <p>Content of Tab Pane 2</p>
  15. </TabPane>
  16. <TabPane tab="Tab Title 3" key="3">
  17. <p>Content of Tab Pane 3</p>
  18. <p>Content of Tab Pane 3</p>
  19. <p>Content of Tab Pane 3</p>
  20. </TabPane>
  21. </Tabs>
  22. </div>,
  23. mountNode,
  24. );
  1. .card-container > .ant-tabs-card > .ant-tabs-content {
  2. height: 120px;
  3. margin-top: -16px;
  4. }
  5. .card-container > .ant-tabs-card > .ant-tabs-content > .ant-tabs-tabpane {
  6. background: #fff;
  7. padding: 16px;
  8. }
  9. .card-container > .ant-tabs-card > .ant-tabs-bar {
  10. border-color: #fff;
  11. }
  12. .card-container > .ant-tabs-card > .ant-tabs-bar .ant-tabs-tab {
  13. border-color: transparent;
  14. background: transparent;
  15. }
  16. .card-container > .ant-tabs-card > .ant-tabs-bar .ant-tabs-tab-active {
  17. border-color: #fff;
  18. background: #fff;
  19. }

Tabs标签页 - 图11

自定义新增页签触发器

隐藏默认的页签增加图标,给自定义触发器绑定事件。

  1. import { Tabs, Button } from 'antd';
  2. const { TabPane } = Tabs;
  3. class Demo extends React.Component {
  4. constructor(props) {
  5. super(props);
  6. this.newTabIndex = 0;
  7. const panes = [
  8. { title: 'Tab 1', content: 'Content of Tab Pane 1', key: '1' },
  9. { title: 'Tab 2', content: 'Content of Tab Pane 2', key: '2' },
  10. ];
  11. this.state = {
  12. activeKey: panes[0].key,
  13. panes,
  14. };
  15. }
  16. onChange = activeKey => {
  17. this.setState({ activeKey });
  18. };
  19. onEdit = (targetKey, action) => {
  20. this[action](targetKey);
  21. };
  22. add = () => {
  23. const { panes } = this.state;
  24. const activeKey = `newTab${this.newTabIndex++}`;
  25. panes.push({ title: 'New Tab', content: 'New Tab Pane', key: activeKey });
  26. this.setState({ panes, activeKey });
  27. };
  28. remove = targetKey => {
  29. let { activeKey } = this.state;
  30. let lastIndex;
  31. this.state.panes.forEach((pane, i) => {
  32. if (pane.key === targetKey) {
  33. lastIndex = i - 1;
  34. }
  35. });
  36. const panes = this.state.panes.filter(pane => pane.key !== targetKey);
  37. if (panes.length && activeKey === targetKey) {
  38. if (lastIndex >= 0) {
  39. activeKey = panes[lastIndex].key;
  40. } else {
  41. activeKey = panes[0].key;
  42. }
  43. }
  44. this.setState({ panes, activeKey });
  45. };
  46. render() {
  47. return (
  48. <div>
  49. <div style={{ marginBottom: 16 }}>
  50. <Button onClick={this.add}>ADD</Button>
  51. </div>
  52. <Tabs
  53. hideAdd
  54. onChange={this.onChange}
  55. activeKey={this.state.activeKey}
  56. type="editable-card"
  57. onEdit={this.onEdit}
  58. >
  59. {this.state.panes.map(pane => (
  60. <TabPane tab={pane.title} key={pane.key}>
  61. {pane.content}
  62. </TabPane>
  63. ))}
  64. </Tabs>
  65. </div>
  66. );
  67. }
  68. }
  69. ReactDOM.render(<Demo />, mountNode);

Tabs标签页 - 图12

自定义页签头

使用 react-sticky 组件实现吸顶效果。

  1. import { Tabs } from 'antd';
  2. import { StickyContainer, Sticky } from 'react-sticky';
  3. const { TabPane } = Tabs;
  4. const renderTabBar = (props, DefaultTabBar) => (
  5. <Sticky bottomOffset={80}>
  6. {({ style }) => (
  7. <DefaultTabBar {...props} className="site-custom-tab-bar" style={{ ...style }} />
  8. )}
  9. </Sticky>
  10. );
  11. ReactDOM.render(
  12. <StickyContainer>
  13. <Tabs defaultActiveKey="1" renderTabBar={renderTabBar}>
  14. <TabPane tab="Tab 1" key="1" style={{ height: 200 }}>
  15. Content of Tab Pane 1
  16. </TabPane>
  17. <TabPane tab="Tab 2" key="2">
  18. Content of Tab Pane 2
  19. </TabPane>
  20. <TabPane tab="Tab 3" key="3">
  21. Content of Tab Pane 3
  22. </TabPane>
  23. </Tabs>
  24. </StickyContainer>,
  25. mountNode,
  26. );
  1. .site-custom-tab-bar {
  2. z-index: 1;
  3. background: #fff;
  4. }

Tabs标签页 - 图13

可拖拽标签

使用 react-dnd 实现标签可拖拽。

  1. import { Tabs } from 'antd';
  2. import { DndProvider, DragSource, DropTarget } from 'react-dnd';
  3. import HTML5Backend from 'react-dnd-html5-backend';
  4. const { TabPane } = Tabs;
  5. // Drag & Drop node
  6. class TabNode extends React.Component {
  7. render() {
  8. const { connectDragSource, connectDropTarget, children } = this.props;
  9. return connectDragSource(connectDropTarget(children));
  10. }
  11. }
  12. const cardTarget = {
  13. drop(props, monitor) {
  14. const dragKey = monitor.getItem().index;
  15. const hoverKey = props.index;
  16. if (dragKey === hoverKey) {
  17. return;
  18. }
  19. props.moveTabNode(dragKey, hoverKey);
  20. monitor.getItem().index = hoverKey;
  21. },
  22. };
  23. const cardSource = {
  24. beginDrag(props) {
  25. return {
  26. id: props.id,
  27. index: props.index,
  28. };
  29. },
  30. };
  31. const WrapTabNode = DropTarget('DND_NODE', cardTarget, connect => ({
  32. connectDropTarget: connect.dropTarget(),
  33. }))(
  34. DragSource('DND_NODE', cardSource, (connect, monitor) => ({
  35. connectDragSource: connect.dragSource(),
  36. isDragging: monitor.isDragging(),
  37. }))(TabNode),
  38. );
  39. class DraggableTabs extends React.Component {
  40. state = {
  41. order: [],
  42. };
  43. moveTabNode = (dragKey, hoverKey) => {
  44. const newOrder = this.state.order.slice();
  45. const { children } = this.props;
  46. React.Children.forEach(children, c => {
  47. if (newOrder.indexOf(c.key) === -1) {
  48. newOrder.push(c.key);
  49. }
  50. });
  51. const dragIndex = newOrder.indexOf(dragKey);
  52. const hoverIndex = newOrder.indexOf(hoverKey);
  53. newOrder.splice(dragIndex, 1);
  54. newOrder.splice(hoverIndex, 0, dragKey);
  55. this.setState({
  56. order: newOrder,
  57. });
  58. };
  59. renderTabBar = (props, DefaultTabBar) => (
  60. <DefaultTabBar {...props}>
  61. {node => (
  62. <WrapTabNode key={node.key} index={node.key} moveTabNode={this.moveTabNode}>
  63. {node}
  64. </WrapTabNode>
  65. )}
  66. </DefaultTabBar>
  67. );
  68. render() {
  69. const { order } = this.state;
  70. const { children } = this.props;
  71. const tabs = [];
  72. React.Children.forEach(children, c => {
  73. tabs.push(c);
  74. });
  75. const orderTabs = tabs.slice().sort((a, b) => {
  76. const orderA = order.indexOf(a.key);
  77. const orderB = order.indexOf(b.key);
  78. if (orderA !== -1 && orderB !== -1) {
  79. return orderA - orderB;
  80. }
  81. if (orderA !== -1) {
  82. return -1;
  83. }
  84. if (orderB !== -1) {
  85. return 1;
  86. }
  87. const ia = tabs.indexOf(a);
  88. const ib = tabs.indexOf(b);
  89. return ia - ib;
  90. });
  91. return (
  92. <DndProvider backend={HTML5Backend}>
  93. <Tabs renderTabBar={this.renderTabBar} {...this.props}>
  94. {orderTabs}
  95. </Tabs>
  96. </DndProvider>
  97. );
  98. }
  99. }
  100. ReactDOM.render(
  101. <DraggableTabs>
  102. <TabPane tab="tab 1" key="1">
  103. Content of Tab Pane 1
  104. </TabPane>
  105. <TabPane tab="tab 2" key="2">
  106. Content of Tab Pane 2
  107. </TabPane>
  108. <TabPane tab="tab 3" key="3">
  109. Content of Tab Pane 3
  110. </TabPane>
  111. </DraggableTabs>,
  112. mountNode,
  113. );

API

Tabs

参数说明类型默认值
activeKey当前激活 tab 面板的 keystring
animated是否使用动画切换 Tabs,在 tabPosition=top|bottom 时有效boolean | {inkBar:boolean, tabPane:boolean}true, 当 type="card" 时为 false
renderTabBar替换 TabBar,用于二次封装标签头(props: DefaultTabBarProps, DefaultTabBar: React.ComponentClass) => React.ReactElement
defaultActiveKey初始化选中面板的 key,如果没有设置 activeKeystring第一个面板
hideAdd是否隐藏加号图标,在 type="editable-card" 时有效booleanfalse
size大小,提供 large defaultsmall 三种大小string'default'
tabBarExtraContenttab bar 上额外的元素React.ReactNode
tabBarGuttertabs 之间的间隙number
tabBarStyletab bar 的样式对象object-
tabPosition页签位置,可选值有 top right bottom leftstring'top'
type页签的基本样式,可选 linecard editable-card 类型string'line'
onChange切换面板的回调Function(activeKey) {}
onEdit新增和删除页签的回调,在 type="editable-card" 时有效(targetKey, action): void
onNextClicknext 按钮被点击的回调Function
onPrevClickprev 按钮被点击的回调Function
onTabClicktab 被点击的回调Function

Tabs.TabPane

参数说明类型默认值
forceRender被隐藏时是否渲染 DOM 结构booleanfalse
key对应 activeKeystring
tab选项卡头显示文字string|ReactNode