實作一個 Tabs 元件
我們已經談論了夠多關於 React 的用法和細節了,在學習的過程中小弟始終認為實作便是最佳的學習捷徑,所以這一次讓我們搭配一些時常會遇到的小需求實作 React 元件吧!
複合式(組合)元件
在 React 中任何東西都是元件,就像樂高一樣,你可以用小片的積木組成大塊的,再組合出您想到的東西。
同樣的道理您也可以用許多的小元件(小功能模組)來組合出您的應用程式。所謂的複合式元件或稱作組合元件,
他其實就是由多個元件去組成一個多功能的大元件。
在這篇文章我們要來建立一個 tabs 標簽切換功能的元件,為了達成這個功能我們需要 4 個不同的元件:
<Tabs />, <TabList />, <Tab /> 和 <TabPanel /> 分別用來呈現整個 tabs , 列出
標簽列,標簽列的按鈕,以及顯示的內容。結構如下:
<Tabs>
<TabList>
<Tab>Iron man</Tab>
<Tab>Superman</Tab>
<Tab>Lucy</Tab>
</TabList>
<TabPanel>
鋼鐵人介紹
</TabPanel>
<TabPanel>
超人介紹
</TabPanel>
<TabPanel>
鹿茸介紹
</TabPanel>
</Tabs>
首先是 <Tabs/> 的行為,它被用來當做一個容器,其角色有點像是一個 controller ,因為它必須要掌管所有 DOM 的事件(點擊 Tab 切換至該內容,被選取到的 index)
同時也需要管理 state 看看哪個 <Tab/> 目前正被選取到,所以我們會稱 <Tabs /> 為擁有者元件(owner component)。
我們遭遇到的第一個挑戰是: 元件之間該如何溝通。每一個元件都有一個 state 。每當 state 發生變動,React 就會更新並重新渲染元件以使其跟 state 一致。
當我們選了某個索引後,<Tabs/> 元件就要去更新 <Tab/> 和 <TabPanel/> 的 state。
典範轉移
首先我們為每一個元件建立一個 API,透過建立一個方法來變更 state。在 React 中一個元件可以透過 this.props.children 去存取子元件。
所以一開始我們理論上只要使用 handleSelected 去設定適當該顯示的子元件如下:
var tabs = this.props.children[0].props.children,
panels = this.props.children.slice(1),
index = this.state.selectedIndex;
tabs[index].handleSelected(true);
panels[index].handleSelected(true);
這樣的做法在 v0.10.0 以前的版本是可以運作的,概略的實作如下:
不過到了 React v0.10.0 版本的時候這樣做會出現警告:
Invalid access to component property "setSelected"
到了 v0.11.0 的時候更慘您已經無法直接存取子元件的方法,因為是新版的 React [i]this.props.children 回傳的物件只是描述物件(discriptors)。不再是對應元件的參考物件,且官方建議您不應該直接存取子元件的實際物件。
Component Refs
React 提供一種機制給你取得實際元件的物件,就是使用 refs
var App = React.createClass({
handleClick: function () {
alert(this.refs.myInput.getDOMNode().value);
},
render: function () {
return (
<div>
<input ref="myInput"/>
<button
onClick={this.handleClick}>
Submit
</button>
</div>
);
}
});
透過 refs 屬性您就可以取得該子元件的參考
動態的子元件
典型的 refs 使用方式是父元件已經知道子元件的情況,所以可以直接在 tag 中指定 ref 如上面的範例。
不過這次我們希望我們的 Tabs 元件可以動態的放入 <Tab/> 和 <TabPanel/>
例如:
var App = React.createClass({
render: function () {
return (
<div className='container'>
<Tabs>
<TabList>
<Tab name='ironman' >Iron man</Tab>
<Tab name='superman' >Superman</Tab>
<Tab name='lucy' >Lucy</Tab>
</TabList>
<TabPanel name='panel-ironman'>
鋼鐵人
</TabPanel>
<TabPanel name='panel-superman'>
超人再起
</TabPanel>
<TabPanel name='panel-lucy'>
露西
</TabPanel>
</Tabs>
</div>
);
}
});
而 Tabs 只是動態地把開發者加入的任意結構輸出
var Tabs = React.createClass({
render: function () {
return (
<div className='react-tabs'>
{this.props.children}
</div>
);
}
});
因此我們需要一些動態指定 refs 的方法,而這個方法就是透過 cloneWithProps
var App = React.createClass({
render: function () {
var index = 0,
children = React.Children.map(this.props.children, function (child) {
return React.addons.cloneWithProps(child, {
ref: 'child-' + (index++)
});
});
return (
<div>
{children}
</div>
);
}
});_