読者です 読者をやめる 読者になる 読者になる

vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

React.jsに触れてみる2

React.js Javascript

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

JS内に記述するCSS