イベント処理

イベント処理

Reactのイベント処理は DOM 要素のイベントの処理と似ているが、イベント処理文法的な違いがある。

  • camelCaseで名付ける
  • JSX では関数を渡す
  • デフォルト動作の抑止に preventDefault を使う

HTML

<button onclick="activeLases()">
  Active Lasers
</button>

React

<button onClick={activeLases}>
  Active Lasers
</button>

デフォルトの動作を抑止するには、 preventDefault が必須。新しいページを開くというリンクの動作を例に考えてみる。

React は既存のプロジェクトに徐々に追加していけるのでした。今回は、既存の書き方に React を追加してみる。 HTML

    <div id="root"></div>
    <a
      href="https://github.com/facebook/react"
      onclick="console.log('The link was clicked.'); return true"
      >Click me: Vanilla JavaScript case ture</a
    >
    <br />
    <a
      href="https://github.com/facebook/react"
      onclick="console.log('The link was clicked.'); return false"
      >Click me: Vanilla JavaScript case false</a
    >
    <script type="text/babel">
      function ActionLink() {
        function handleClick(e) {
          e.preventDefault();
          console.log('The link was clicked.');
        }

        return (
          <a href="https://github.com/facebook/react" onClick={handleClick}>
            Click me: React.js
          </a>
        );
      }

      ReactDOM.render(<ActionLink />, document.getElementById('root'));
    </script>

e は合成イベント。ブラウザ間の互換性を心配する必要はない。ネイティブのイベントと React のイベントは同様に動作するのではない。

理解できていない。初心者は細かいことを気にせず、 e を使えばいいだろう。

要素が最初にレンダーされる際にリスナを指定する。

一般的なパターンではイベントハンドラはクラスのメソッドになる。

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isToggleOn: true };
    // このバインディングは、コールバックで `this`を機能させるために必要
    this.handleClick = this.handleClick.bind(this);
    // this.handleClick = handleClick;
    console.log(this);
  }
  // Event listener
  handleClick() {
    this.setState((prevState) => ({
      isToggleOn: !prevState.isToggleOn,
    }));
  }
  render() {
    // onClick属性にイベントを添付
    return <button onClick={this.handleClick}>{this.state.isToggleOn ? 'ON' : 'OFF'}</
button>;
  }
}
ReactDOM.render(<Toggle />, document.getElementById('root'));

クラスのメソッドはデフォルトではバインドされない。

bind 呼び出しを回避する方法の1つは、クラスフィールドを使うこと。

class LoggingButton extends React.Component {
  // This syntax ensures `this` is bound within handleClick.
  // Warning: this is *experimental* syntax.
  handleClick = () => {
    console.log('this is:', this);
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}

パブリッククラスフィールド

パブリックスタティックフィールドとパブリックインスタンスフィールドは、書き込み可能、列挙可能、設定可能なプロパティです。

を回避する方法の2つめは、コールバック内でアロー関数を使うこと。

class LoggingButton extends React.Component {
  handleClick() {
    console.log('this is:', this);
  }

  render() {
    // This syntax ensures `this` is bound within handleClick
    return (
      <button onClick={() => this.handleClick()}>
        Click me
      </button>
    );
  }
}

コールバックが props の下層のコンポーネントに渡される場合、余分に再描画される。コンストラクタでバインドするかクラスフィールド構文を使用して問題を避けること。

useStat を使う

ここまで見てきた this の問題は hooks の1つである useState を使って回避できる。クラスコンポーネントでは、オブジェクトであった状態管理が、 hooks を使うことによって、変数とこの変数を更新する専用関数になるからです。

const Toggle = () => {
  const [toggle, setToggle] = React.useState(true);
  return (
    <div>
      <button onClick={() => setToggle(!toggle)}>{toggle ? 'ON' : 'OFF'}</
button>
    </div>
  );
};
ReactDOM.render(<Toggle />, document.getElementById('root'));

私は初心者なので、 hooks を使えば、本当にパフォーマンスの問題を避けられているのかわからない。

イベントハンドラに引数を渡す

<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

上記の 2 行は等価である。

React イベントを表す引数 eID の 2 番目の引数として渡される。 bind の場合には id 以降の引数は自動的に転送される。

Function.prototype.bind が使われている下行に e がないのに引数として渡されることに違和感しかありません。 Rest parameters (残余引数) の形をとっていれば納得もできるのですが...

条件付きレンダー

条件付きレンダーJavaScript における条件分岐と同じように動作する。

if や条件演算子を使って現在の状態を表す要素を作成すれば、 React は一致するように UI を高維新する。

以下の 2 つのコンポーネントで考える。

function UserGreeting(props) {
  return <h1>Welcome back!</h1>;
}

function GuestGreeting(props) {
  return <h1>Please sign up.</h1>;
}

ユーザがログインしているかどうかによって、これらのコンポーネントの一方だけを表示する Greeting コンポーネントを作成する。

function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  if (isLoggedIn) {
    return <UserGreeting />;
  }
  return <GuestGreeting />;
}
ReactDOM.render(<Greeting isLoggedIn={true} />, document.
getElementById('root'));
// ReactDOM.render(<Greeting isLoggedIn={false} />, document.
getElementById('root'));

要素変数

要素を保持しておくために変数を使うことができる。出力の他の部分を変えずにコンポーネントの一部を条件付きでレンダーしたい時に役立つ。

ログアウトとログインボタンを表す2つのコンポーネントで考える。

function LoginButton(props) {
  return (
    <button onClick={props.onClick}>
      Login
    </button>
  );
}

function LogoutButton(props) {
  return (
    <button onClick={props.onClick}>
      Logout
    </button>
  );
}

LoginControl というステート付きコンポーネントを作成する。

LoginControl コンポーネントは、現在の state によって <LoginButton /> または <LogoutButton /> の一方をレンダーする。前の例の <Greeting /> もレンダーする。

      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 button;

          if (isLoggedIn) {
            button = <LogoutButton onClick={this.handleLogoutClick} />;
          } else {
            button = <LoginButton onClick={this.handleLoginClick} />;
          }

          return (
            <div>
              <Greeting isLoggedIn={isLoggedIn} />
              {button}
            </div>
          );
        }
      }

      function UserGreeting(props) {
        return <h1>Welcome back!</h1>;
      }

      function GuestGreeting(props) {
        return <h1>Please sign up.</h1>;
      }

      function Greeting(props) {
        const isLoggedIn = props.isLoggedIn;
        if (isLoggedIn) {
          return <UserGreeting />;
        }
        return <GuestGreeting />;
      }

      function LoginButton(props) {
        return <button onClick={props.onClick}>Login</button>;
      }

      function LogoutButton(props) {
        return <button onClick={props.onClick}>Logout</button>;
      }

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

条件付きでレンダーの核心部分は以下の通り。

if (isLoggedIn) {
  button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
  button = <LoginButton onClick={this.handleLoginClick} />;
}

JSXでインラインで条件を記述するより短い構文がある。

論理 && 演算子によるインライン If

JSX に式として埋め込むに {} で囲むこと。条件に応じて要素を含めたいときに便利。

      const Mailbox = ({ unreadMessages }) => {
        const messages = unreadMessages;
        return (
          <div>
            <h1>Hello!</h1>
            {messages.length > 0 && <h2>You have {messages.length} unread messages.</h2>}
          </div>
        );
      };

      const messages = ['React', 'Re: React', 'Re:Re: React'];
      // const messages = [];
      ReactDOM.render(<Mailbox unreadMessages={messages} />, document.getElementById('root'));
  • true && expression --> 必ず expression が評価される
  • false && expresiion --> 必ず false が評価される
  • falsy な値の場合、 && の後の評価はスキップするが、 falsy な値そのものは返される

falsyな値は何があったかな?私を モダンJavaScript の世界に羽ばたかせてくれた JavaScript Primer のここを読み返せば解決です。

論理演算子

インライン If-Else

条件的に要素をレンダーするもうひとつの方法は condition ? true : false 条件演算子を利用すること。

render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
    </div>
  );
}

三項演算子ですね。短く書けるので便利に使っています。

条件が複雑すぎると考えたら、コンポーネントを抽出するタイミングかもしれないとのこと。

重要な事柄がさらっと紹介されている。

コンポーネントのレンダーを防ぐ

コンポーネントが他のコンポーネントによりレンダーされているが、自コンポーネントを隠すには、レンダー出力の代わりに null を返す。

WarningBanner コンポーネントwarn プロパティの値に応じて、プロパティの値が false ならレンダーされない。

コンポーネントrender から null を返してもライフサイクルに影響はしない。

コードが随分長くなってきました。たまには、スタイリングもしてみましょう。

CodePen から CSS を拝借。

      const button = {
        height: '40px',
        width: '200px',
      };
      const warning = {
        backgroundColor: '#ff0000',
        textAlign: 'center',
        width: '100%',
        padding: '10px',
      };

      function WarningBanner(props) {
        if (!props.warn) {
          return null;
        }

        return (
          <div className="warning" style={warning}>
            Warning!
          </div>
        );
      }

      class Page extends React.Component {
        constructor(props) {
          super(props);
          this.state = { showWarning: true };
          this.handleToggleClick = this.handleToggleClick.bind(this);
        }

        handleToggleClick() {
          this.setState((prevState) => ({
            showWarning: !prevState.showWarning,
          }));
        }

        render() {
          return (
            <div>
              <WarningBanner warn={this.state.showWarning} />
              <button onClick={this.handleToggleClick} style={button}>
                {this.state.showWarning ? 'Hide' : 'Show'}
              </button>
            </div>
          );
        }
      }

      ReactDOM.render(<Page />, document.getElementById('root'));