一、首先是Showcase@H_301_1@
See the Pen react-tabs by Wang Chao (@charleyw) on CodePen.
二、如何实现@H_301_1@
既然用React写,那么它就必然是一个组件,首先考虑你怎么使用这个组件,也就是这个组件的接口是怎么样的。
<TabsControl> <Tab name="red"> <div className="red"/> </Tab> <Tab name="blue"> <div className="blue"/> </Tab> <Tab name="yellow"> <div className="yellow"/> </Tab> </TabsControl>
这个TabsControl
作为父组件,它来控制Tab
的如何切换,Tab
是用来包裹真正要显示的内容的,它的name
属性是这个标签页的名字,会被显示在标签页的标题栏上。
三、创建基本元素@H_301_1@
按照之前的想法,我们用Tab
定义了很多个标签页,我们需要根据这些定义生成标签页的标题栏和内容。
1. 遍历this.props.children
动态生成标题栏
this.props.children
是React内建的一个属性,用来获取组件的子元素。因为子元素有可能是Object或者Array,所以React提供了一些处理children的辅助方法用来遍历:React.Children.map
React.Children.map(this.props.children,(element,index) => { return (<div className="tab-title-item">{element.props.name}</div>)
2. 再用同样方法生成标签页内容
React.Children.map(this.props.children,element => { return (element) })
组合起来就是TabsControl
的实现:
let TabsControl = React.createClass({ render: function () { let that = this; let {baseWidth} = this.props; let childrenLength = this.props.children.length; return ( <div> <nav className="tab-title-items"> {React.Children.map(this.props.children,index) => { return (<div className="tab-title-item">{element.props.name}</div>) })} </nav> <div className="tab-content-items"> {React.Children.map(this.props.children,element => { return (element) })} </div> </div> ) } });
三、实现标签页切换@H_301_1@
这里要出现React最重要的概念了state
,state
是一个Javascript的Object,它是用来表示组件的当前状态的,如果用TabsControl
举例的话,它的state
可以是当前处于激活状态的标签页编号(当然,如果你想的话也可以保存标签页的内容)。
React提供了一个方法setState()
让你可以改变state
的值。每次调用setState()
都会触发组件的render()
,也就是说会把组件所代表的DOM更新到state
所代表的状态。
所以实现切换的关键如下:
state
保存当前处于激活状态的标签页的编号render()
的时候,在遍历this.props.children
的时候把编号与state
中编号一致的元素标记为active
用css将非
active
的元素隐藏起来
所以代码是这样的:
let TabsControl = React.createClass({ getInitialState: function(){ return {currentIndex: 0} },getTitleItemCssClasses: function(index){ return index === this.state.currentIndex ? "tab-title-item active" : "tab-title-item"; },getContentItemCssClasses: function(index){ return index === this.state.currentIndex ? "tab-content-item active" : "tab-content-item"; },render: function(){ let that = this; let {baseWidth} = this.props; let childrenLength = this.props.children.length; return ( <div> <nav className="tab-title-items"> {React.Children.map(this.props.children,index) => { return (<div onClick={() => {this.setState({currentIndex: index})}} className={that.getTitleItemCssClasses(index)}>{element.props.name}</div>) })} </nav> <div className="tab-content-items"> {React.Children.map(this.props.children,index) => { return (<div className={that.getContentItemCssClasses(index)}>{element}</div>) })} </div> </div> ) } });
getInitialState
:是组件的初始化状态,默认是第一个标签页处于激活状态。getTitleItemCssClasses
:判断当前标签和state
中保存的标签编号是否一直,是则标识为active
。getContentItemCssClasses
:同上。onClick={() => {this.setState({currentIndex: index})}}
:标签页标题绑定了点击事件,每次点击都会更新state
保存的标签页编号,然后触发render()
方法重绘标签页。
四、总结@H_301_1@
上面一系列的操作最终的结果都需要用render()
来反应出来,所以关键点是如何在render()
中使用state
来动态生成DOM.
接下来的改进
实现可以滑动的标签页