Tabs组件分为Tabs,TabPane,TabNav,TabContent,本demo使用create-react-app创建

Tips

组件属性

Tabs

  • classPrefix 样式前缀名
  • defaultActiveIndex 默认选中Tab
  • activeIndex 选中的Tab
  • onChange Tab切换式的回调函数 onChange({ activeIndex, prevIndex })

TabPanel

  • order TabPanel标识
  • tab TabNav中显示的内容(支持ReactElement)
  • disabled Tab不可选中

status

  • activeIndex 当前选中的Tab
  • prevIndex 上次选中的Tab

组件使用

1
2
3
4
5
<Tabs defaultActiveIndex={1} activeIndex={1} onChange={(activeIndex, prevIndex) => {}}>
<TabPanel order={1} tab='tab1'>这是tab1</TabPanel>
<TabPanel order={2} tab='tab2'>这是tab2</TabPanel>
<TabPanel order={3} tab='tab3' disabled >这是tab3</TabPanel>
</Tabs>

Tabs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import React, { Component, PropTypes, cloneElement } from 'react';
import classnames from 'classnames';
import TabNav from './TabNav';
import TabContent from './TabContent';
import './index.scss'
class Tabs extends Component {
static defaultProps = {
classPrefix: 'tabs',
onChange: () => { }
}
constructor(props) {
super(props)
const currProps = this.props
let activeIndex
// 初始化 activeIndex state
if ('activeIndex' in currProps) {
activeIndex = currProps.activeIndex
} else if ('defaultActiveIndex' in currProps) {
activeIndex = currProps.defaultActiveIndex
}
this.state = {
activeIndex,
prevIndex: activeIndex
}
}
componentWillReceiveProps(nextProps) {
// 如果props传入activeIndex则直接更新
if ('activeIndex' in nextProps) {
this.setState({
activeIndex: nextProps.activeIndex
})
}
}
handleTabClick = (activeIndex) => {
const prevIndex = this.state.activeIndex
// 如果当前activeIndex与传入的activeIndex不一致
// 并且 props 中存在 defaultActiveIndex 时, 则更新
if (this.state.activeIndex !== activeIndex && 'defaultActiveIndex' in this.props) {
this.setState({
activeIndex,
prevIndex
})
// 更新后执行回调函数,抛出当前索引和上一次索引
this.props.onChange({ activeIndex, prevIndex })
}
}
renderTabNav = () => {
const { classPrefix, children } = this.props
return (
<TabNav
key='tabBar'
classPrefix={classPrefix}
onTabClick={this.handleTabClick}
panels={children}
activeIndex={this.state.activeIndex}
/>
)
}
renderTabContent = () => {
const { classPrefix, children } = this.props
return (
<TabContent
key='tabContent'
classPrefix={classPrefix}
panels={children}
activeIndex={this.state.activeIndex}
/>
)
}
render() {
const { className } = this.props
const classes = classnames(className, 'ui-tabs')
return (
<div className={classes}>
{this.renderTabNav()}
{this.renderTabContent()}
</div>
);
}
}
export default Tabs;

TabPane

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React, { Component } from 'react';
import classnames from 'classnames'
class TabPane extends Component {
render() {
const { className, classPrefix, isActive, children } = this.props
const classes = classnames({
[className]: className,
[`${classPrefix}-panel`]: true,
[`${classPrefix}-active`]: isActive
})
return (
<div
role='tabpanel'
className={classes}
aria-hidden={!isActive}
>
{children}
</div>
);
}
}
export default TabPane;

TabNav

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import React, { Component } from 'react';
import classnanes from 'classnames';
import './index.scss'
class TavNav extends Component {
getTabs = () => {
const { panels, classPrefix, activeIndex } = this.props
return React.Children.map(panels, (child) => {
if (!child) return
const order = parseInt(child.props.order, 10)
// 利用class控制显示和隐藏
let classes = classnanes({
[`${classPrefix}-tab`]: true,
[`${classPrefix}-active`]: activeIndex === order,
[`${classPrefix}-disabled`]: child.props.disabled
})
let events = {}
if (!child.props.disabled) {
events = {
onClick: this.props.onTabClick.bind(this, order)
}
}
const ref = {}
if (activeIndex === order) {
ref.ref = 'avtiveTab'
}
return (
<li
role='tab'
aria-disabled={child.props.disabled ? 'true' : 'false'}
aria-selected={activeIndex === order ? 'true' : 'false'}
{...events}
className={classes}
key={order}
{...ref}
>
{child.props.tab}
</li>
)
})
}
render() {
const { classPrefix } = this.props
const rootClasses = classnanes({
[`${classPrefix}-bar`]: true
})
const classes = classnanes({
[`${classPrefix}-nav`]: true
})
return (
<div className={rootClasses}>
<ul className={classes}>
{this.getTabs()}
</ul>
</div>
);
}
}
export default TavNav;

TabContent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import React, { Component } from 'react';
import classnames from 'classnames'
class TabContent extends Component {
getTabPanes = () => {
const { classPrefix, activeIndex, panels } = this.props
return React.Children.map(panels, (child) => {
if (!child) return
const order = parseInt(child.props.order, 10)
const isActive = order === activeIndex
return React.cloneElement(child, {
classPrefix,
isActive,
children: child.props.children,
key: `tabpane-${order}`
})
})
}
render() {
const { classPrefix } = this.props
const classes = classnames({
[`${classPrefix}-content`]: true
})
return (
<div className={classes}>
{this.getTabPanes()}
</div>
);
}
}
export default TabContent;

样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
$class-prefix: 'tabs';
.#{$class-prefix} {
&-bar {
margin-bottom: 16px;
}
&-nav {
font-size: 14px;
&:after,
&::before {
display: table;
content: '';
}
&::after {
clear: both;
}
}
&-nav>&-tab {
float: left;
list-style: none;
margin-right: 24px;
padding: 8px 20px;
text-decoration: none;
color: #666;
cursor: pointer;
}
&-nav>&-active {
border-bottom: 2px solid #00c49f;
color: #00c49f;
cursor: pointer;
}
&-nav>&-disabled {
cursor: no-drop;
}
&-content &-panel {
display: none;
}
&-content &-active {
display: block;
}
}

webpack配置scss

  • 安装所需模块

    1
    yarn add node-sass sass-loader --dev
  • 配置webpack.config.dev.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {
    exclude: [/\.js$/, /\.html$/, /\.json$/, /\.scss$/],
    loader: require.resolve('file-loader'),
    options: {
    name: 'static/media/[name].[hash:8].[ext]',
    },
    },
    // scss loader
    {
    test: /\.scss$/,
    use: ['style-loader', 'css-loader', 'sass-loader']
    }