はじめてのReact #9「状態に合わせてComponentを切り替える」

Componentの状態をthis.setState()で変化させる度にrender()が自動的に実行され最新の描画がされるのですが、Reactでは単純なHTMLだけではなくComponent自体を別の物に切り替えることができます。

今回はReactの公式ドキュメント「Conditional Rendering」を単純化したサンプルコードでお送りしします。

ログインComponent

よくあるログインとログアウトの管理用のComponentをイメージしたサンプルになっています。最初はログインしていない状態ですが、ボタンをクリックすることでログイン状態になります。再度ボタンをクリックすると最初の状態(ログアウト)に戻るというシンプルなものです。

サンプル

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <title>LoginControl</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 LoginControl extends React.Component {
      constructor(props) {
        super(props);

        this.handleLoginClick  = this.handleLoginClick.bind(this);
        this.handleLogoutClick = this.handleLogoutClick.bind(this);

        this.state = {
          isLoggedIn: false
        };
      }

      handleLoginClick() {
        this.setState({isLoggedIn: true});
      }
      handleLogoutClick() {
        this.setState({isLoggedIn: false});
      }

      render() {
        const isLoggedIn = this.state.isLoggedIn;
        let message;
        let button;

        if (isLoggedIn) {
          message = "ナイス、ログイン";
          button  = <LogoutButton onClick={this.handleLogoutClick} />;
        }
        else {
          message = "ログインしてください";
          button  = <LoginButton onClick={this.handleLoginClick} />;
        }

        return (
          <div>
            <h1>{message}</h1>
            {button}
          </div>
        );
      }
    }

    function LogoutButton(props) {
      return <button onClick={props.onClick}>ログアウト</button>;
    }

    function LoginButton(props) {
      return <button onClick={props.onClick}>ログイン</button>;
    }

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

実行結果

前述の通り最初はログインしていない状態ですが、ボタンをクリックすると

こちらログイン状態になります。もう一度ボタンをクリックすればログアウトし、最初の状態に戻ります。

解説

thisの罠を回避する

コンストラクタに見慣れない部分が出てきました。これは何のために必要なのでしょうか?

  constructor(props) {
    super(props);

    // 下の2行
    this.handleLoginClick  = this.handleLoginClick.bind(this);
    this.handleLogoutClick = this.handleLogoutClick.bind(this);

結論から言うとこれがないとhandleLoginClick()メソッド内で、Stateの変更を行うことができません。 実際にコメントアウトして実行すると以下のようにCannot read property 'setState' of undefinedと実行時エラーが発生してしまいました。

JavaScriptではthisというキーワードが頻繁に出てきますが、これは今現在自分がいるオブジェクトを指してくれる便利なキーワードです。しかし今回のように、他のオブジェクトや関数内で実行されるようなケースだとthisの意味が変わってきてしまいます。LoginControlクラス内のhandleLoginClick()に書かれているthisは、LoginControlのことを指しています。ところが実際にこのメソッドが実行されるのはLogoutButton関数です。ここでthisが別の箇所を指すことになってしまいます。  ※関数の場合だとそもそもthisが機能しないのでundefinedとなります。

これを防ぎ、本来参照したいthisを取得するための物だったというわけです。

Functional Component

Componentにはこれまで利用してきたクラス状のものの他にも、関数を使っても作成することができます。 今回のサンプルでいうと以下が関数を使ったComponentです。

function LogoutButton(props) {
  return <button onClick={props.onClick}>ログアウト</button>;
}

function LoginButton(props) {
  return <button onClick={props.onClick}>ログイン</button>;
}

関数型とクラス型、どちらが優れている劣っているという物ではなく用途に応じて選択します。ポイントとしては、関数型のComponentにはStateやライフサイクルがありません。逆に言えばJSXを返却するだけのシンプルな処理しかしないのであれば、関数で書いてしまった方がスッキリしますし、高速に動作するはずです。

注意点

必ず上位の階層が必要

render()内にある以下の箇所ですが、<div>を消すと構文エラーとなり処理がそこで止まってしまいます。

  return (
    <div>
      <h1>{message}</h1>
      {button}
    </div>
  );

Uncaught SyntaxError: Inline Babel script: Unexpected tokenという内容のエラーが返されます。

今回のように複数の要素を出力する場合、Reactでは必ず上位の階層があることが求められます。最初のころはこれを知らなくて頭を抱えていましたw

書籍

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

参考ページ

reactjs.org