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