React.jsに触れてみる2
3/31追記
Chromeで実行するとエラーが発生していたため、一部コードを修正しました -> React.jsに触れてみる3 (2の修正)
別のネタをはさみましたが、前回の続きです。
やったこと
- Radioボタンで作成したタブをクリックした時にページの内容を表示する
- 上記ページ内に更にボタンを表示して、それをクリックすると上のタブの左右にある矢印ボタン「<」「>」の文字色を変更する
ソースコード(Javascript)
とりあえず今回作成したJavascriptのコードです(いつも通り、HTMLタグの「<」と「>」は「< 」と「 >」に置き換えています)。
javascript.js
(function () {
var _intRightTabNum = 2;
// 動的にCSSを切り替えるのに使用.
var tabBgStyle = {
color: '#0000FF'
};
var ShowTabs = React.createClass({
getInitialState()
{
// Stateの初期値を設定.
return{
allowColor: tabBgStyle,
innerTabs: [{btnId: 'btn_0', lblId: 'btn_label_0', tabNum: 0, text: 'Tab1'}
, {btnId: 'btn_1', lblId: 'btn_label_1', tabNum: 1, text: 'Tab2'}]
};
},
getDefaultProps: function() {
// デフォルトで表示するページ内容を指定する.
return {
content: < div id='page_contents' >Hello< /div >
};
},
moveTab: function(event){
switch (event.target.id){
case 'arrow_button_left':
// 一つ左隣のTabを表示.
_intRightTabNum--;
console.log("left");
break;
case 'arrow_button_right':
// 一つ右隣のTabを表示.
_intRightTabNum++;
console.log("right");
break;
}
},
movePage: function (event) {
switch(event.target.id)
{
case 'tab_0':
// StateとしてセットしているinnerTabsの要素数分ShowInnerTabsを取得する.
var innerTabs = this.state.innerTabs.map((innerTab) => {
return < ShowInnerTabs onClick={this.changeColor} innerTab={innerTab} / >;
});
this.setProps({content:
< div id='page_contents' >
< section id='btn_frame' >
{innerTabs}
< /section >
< /div >});
break;
case 'tab_1':
this.setProps({content: < div id='page_contents' >Hello< /div >});
break;
}
},
changeColor: function (tabNum)
{
switch(tabNum)
{
case 0:
tabBgStyle.color = '#00FF00';
break;
case 1:
tabBgStyle.color = '#FF0000';
break;
}
this.setState({allowColor: tabBgStyle});
},
render: function() {
return (
< section id='tab_frame'>
< input id='arrow_button_left' className='button_arrow' type='button' onClick={this.moveTab} / >
< label className='arrow_label' htmlFor='arrow_button_left' style={this.state.allowColor} ><< /label >
< input id='tab_0' className='tab_radio' type='radio' name='tabs' onChange={this.movePage} / >
< label className='tab_label' htmlFor='tab_0'>タブ1< /label >
< input id='tab_1' className='tab_radio' type='radio' name='tabs' onChange={this.movePage} / >
< label id='tab_label_1' className='tab_label' htmlFor='tab_1' >タブ2< /label >
< input id='arrow_button_right' className='button_arrow' type='button' onClick={this.moveTab} / >
< label className='arrow_label' htmlFor='arrow_button_right' style={this.state.allowColor} >>< /label >
{this.props.content}
< /section >
);
}
});
// ShowTabsの子として呼ばれ、コンテンツの中身を表示する.
var ShowInnerTabs = React.createClass({
propTypes: { // 親から受け取るpropsのデータ型を指定する.
innerTab: React.PropTypes.shape({
btnId: React.PropTypes.string.isRequired,
lblId: React.PropTypes.string.isRequired,
tabNum: React.PropTypes.number.isRequired,
text: React.PropTypes.string.isRequired
}),
onClick: React.PropTypes.func.isRequired
},
_onClick()
{
// Clickイベントを親から管理可能にする.
this.props.onClick(this.props.innerTab.tabNum);
},
render: function() {
return (
< input id={this.props.innerTab.btnId} type='radio' name='btns' onClick={this._onClick} >
< label id={this.props.innerTab.lblId} className='btn_label' htmlFor={this.props.innerTab.btnId} >{this.props.innerTab.text}
< /input >
);
}
});
React.render(< ShowTabs/ >, document.getElementById('main'));
}).call(this);
Componentから別のComponentを呼ぶ
クリックしたタブに合わせて表示するページ内容を変更するため、タブを扱う部分とページ内容を扱う部分を分けておきます。
これら一つ一つの塊がComponentで、propに格納すると自分の子どもとして読み込むことができるようです(まだイマイチ理解できていませんが)。
上記のコードで言うと、タブを扱うComponentが「ShowTabs」、ページ内容を扱うのが「ShowInnerTabs」で、 ShowTabs > renderの「{this.props.content}」の部分に「ShowInnerTabs」を表示しています。
propに値をセットするには「setProps」を使用します。
で、「ShowInnerTabs」を具体的に呼び出しているのがタブをクリックした時に呼ばれるShowTabs > movePageです。
後述しますが、ShowInnerTabsで使用するInput要素、Label要素のIDやタブ番号、クリックイベント(onClick)を渡した上でsetPropsでpropにデータをせっとしています。
Component間のデータのやりとり
イベントや動的に変化する値などは、Component間で受け渡しをする必要があります。
そこで使用されるのが先ほど触れたpropと、stateです。
両者とも同じようにnumberやstringなどのデータや、Componentが扱えますが、propは変更されない値、stateは動的に変更されうる値を扱うようです。
また、子のComponentで使用する値は、親はstate、子ではpropとして使用しています。
受け渡しをする値は、連想配列として以下のようにセットしておきます。
getInitialState()
{
// Stateの初期値を設定.
return{
〜省略〜
innerTabs: [{btnId: 'btn_0', lblId: 'btn_label_0', tabNum: 0, text: 'Tab1'}
, {btnId: 'btn_1', lblId: 'btn_label_1', tabNum: 1, text: 'Tab2'}]
};
},
上記は、Componentを呼び出すとき(今回は ShowTabs > movePage)に渡してやります。
〜省略〜
movePage: function (event) {
switch(event.target.id)
{
case 'tab_0':
// StateとしてセットしているinnerTabsの要素数分ShowInnerTabsを取得する.
var innerTabs = this.state.innerTabs.map((innerTab) => {
return < ShowInnerTabs onClick={this.changeColor} innerTab={innerTab} / >;
});
this.setProps({content:
< div id='page_contents' >
< section id='btn_frame' >
{innerTabs}
< /section >
< /div >});
break;
case 'tab_1':
〜省略〜
「this.state.innerTabs.map」とmap関数を使用することで、getInitialStateでセットした配列の要素数分Componentを取得してくれるため、 手動で同じHTMLを書く量が減って便利ですね。
その際「onClick」を指定しておき、子のComponentで以下のように書くと親のComponentでクリックイベントを受け取ることができます。
〜省略〜
_onClick()
{
// Clickイベントを親から管理可能にする.
this.props.onClick(this.props.innerTab.tabNum);
},
render: function() {
return (
< input id={this.props.innerTab.btnId} type='radio' name='btns' onClick={this._onClick} >
〜省略〜
思ったこと
最初にどのデータを渡すか、どのようにComponentを切り分けるかというところを考えておかないと、すぐしっちゃかめっちゃかになりそうです...。
この辺りを整理するための考え方がFluxなのかと思ってはいますが、コード自体の書き方も結構異なっているため、まずはこれらを結びつけるところから考える必要がありそうです。
参考
React
- Ajaxを劇的に簡単にするReact.js – @masuidrive blog
- reactjs - React.jsのProp - Qiita
- reactjs - React.jsでPropやStateを使ってComponent間のやりとりをする - Qiita