はじめてのReact #15「Componentの連携」複数同時に処理をする

いよいよReactの真骨頂っぽいところに足を突っ込んでみます。前回はComponentの中にComponentが1つだけ内包されていましたが、これを複数にしてみたいと思います。

西暦・和暦 相互変換Component

今年の5月には新しい年号が爆誕するのにちなんで、西暦と和暦を相互に変換するComponentを作成してみたいと思います。今回も公式ドキュメントのサンプルを改変した物になります。

実行すると昭和、平成、西暦の3つのテキストボックスが表示されます。いずれか1つに入力すると残りの2つが連動して変化します。コードがちょっと長いのですが、理解のしやすさを優先しただけで実際にやってることは大したことありません。

サンプル

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <title>HeiseiConvert</title>
  <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

  <!-- Don't use this in production: -->
  <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
</head>
<body>

<div id="root"></div>

<script type="text/babel">
class YearInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(e) {
    this.props.onYearChange(e.target.value);
  }

  render() {
    const year = this.props.year;
    const type = this.props.type;

    return (
      <fieldset>
        <legend>{type}:</legend>
        <input value={year}
               placeholder={this.props.placeholder}
               onChange={this.handleChange} /> 

      </fieldset>
    );
  }
}

class HeiseiConvert extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
          ad: "",
       showa: "",
      heisei: ""
    };

    this.handleAdChange     = this.handleAdChange.bind(this);
    this.handleShowaChange  = this.handleShowaChange.bind(this);
    this.handleHeiseiChange = this.handleHeiseiChange.bind(this);
  }

  handleAdChange(year) {
    this.setState({
          ad: year,
       showa: this.toConvert(year, -1925),
      heisei: this.toConvert(year, -1989)
    });
  }

  handleShowaChange(year) {
    this.setState({
          ad: this.toConvert(year, 1925),
       showa: year,
      heisei: this.toConvert(year, -63)
    });
  }

  handleHeiseiChange(year) {
    this.setState({
          ad: this.toConvert(year, 1988),
       showa: this.toConvert(year, 63),
      heisei: year
    });
  }

  toConvert(year, value){
    if( Number.isNaN(parseFloat(year)) ){
      return('');
    }
    return( Number(year) + value);
  }

  render() {
    return (
      <div>
        <YearInput
          type="昭和"
          placeholder="例: 64"
          year={this.state.showa}
          onYearChange={this.handleShowaChange} />
        <YearInput
          type="平成"
          placeholder="例: 30"
          year={this.state.heisei}
          onYearChange={this.handleHeiseiChange} />
        <YearInput
          type="西暦"
          placeholder="例: 2019"
          year={this.state.ad}
          onYearChange={this.handleAdChange} />
      </div>
    );
  }
}

ReactDOM.render(
  <HeiseiConvert />,
  document.getElementById('root')
);
</script>
</body>
</html>

実行結果

解説

状態もイベントも親が管理する

思想はシンプルで、入力などは子供のComponentに任せていますがデータの管理はすべて親のComponentであるHeiseiConvertが行っています。テキストボックスに入力された値は最終的にすべて以下のconstructorで初期化しているこのStateに保管されます。

class HeiseiConvert extends React.Component {
  constructor(props) {
    super(props);

    // 状態はすべて以下のStateに入る
    this.state = {
          ad: "",
       showa: "",
      heisei: ""
    };

イベントも同様です。子供のComponentであるYearInputでテキストボックスが作成されていますが、onChangeイベントで呼び出されるのは、最終的に親で定義されているメソッドです。

  render() {
    const year = this.props.year;
    const type = this.props.type;

    return (
      <fieldset>
        <legend>{type}:</legend>
        <input value={year}
               placeholder={this.props.placeholder}
               onChange={this.handleChange} /> 
               年
      </fieldset>
    );
  }
}

上記の onChange={this.handleChange} から呼び出されているメソッドを見ると、Propsで親から渡されたメソッドを実行していることがわかります。

  handleChange(e) {
    this.props.onYearChange(e.target.value);
  }

親が子供のComponentを作成している箇所を見てみると、以下のように親クラス内にあるhandleShowaChangeメソッドを渡しています。

  <YearInput
      type="昭和"
      placeholder="例: 64"
      year={this.state.showa}
      onYearChange={this.handleShowaChange} />

最終的にhandleShowaChangeメソッドが親のStateを更新しているというわけです。

  handleShowaChange(year) {
    this.setState({
          ad: this.toConvert(year, 1925),
       showa: year,
      heisei: this.toConvert(year, -63)
    });
  }

リフティング

公式ドキュメントでは「リフティング」(持ち上げる)という単語が使われていますが、これがイメージとしてわかりやすい。 イベントが発生すると子供はデータを親に対して蹴り上げるます。蹴り上げたデータを親が受け取りStateを更新すると、render()が実行され子供が最新の状態に更新される…つまり親に向かって蹴り上げたボールが再度子供に対して落ちてくる。

以下の図はイメージです。Reactではこの流れをひたすら繰り返す、言わばサッカーのリフティングのような動きを行っているというわけです。

書籍

React入門 React・Reduxの導入からサーバサイドレンダリングによるUXの向上まで (NEXT ONE)
穴井 宏幸 石井 直矢 柴田 和祈 三宮 肇
翔泳社
売り上げランキング: 139,067

参考ページ

reactjs.org