React の基本的な使い方
[最終更新] (2020/10/01 01:10:31)
最近の投稿
注目の記事

概要

こちらのページで環境構築の手順を把握した React について、開発を行うために必要な基本事項について記載します。

JSX 構文で React element を定義

ReactDOM.render(element, container) の第一引数に指定する React element は JSX で記述できます。

const element = <h1>Hello, world!</h1>;

React element は関数から返すことができます。タグは入れ子にすることができます。{} で値を埋め込むこともできます。その場合、属性値はダブルクォーテーション等で囲わないようにします。

function formatName(user) {
  return user.firstName + ' ' + user.lastName;
}

function getGreeting(user) {
  if (user) {
    return <h1 id={user.id}>Hello, {formatName(user)}!</h1>;
  }
  return <h1>Hello, Stranger.</h1>;
}

const user = {
  firstName: 'Harper',
  lastName: 'Perez',
  id: 'myid'
};

const element = (
  <div>
    {getGreeting(user)}
    <h2>Good to see you here.</h2>
  </div>
);

ReactDOM.render(
  element,
  document.getElementById('root')
);

以下のような仮想 DOM が生成されます。

<div id="root">
  <div>
    <h1 id="myid">Hello, Harper Perez!</h1>
    <h2>Good to see you here.</h2>
  </div>
</div>

const で定義していることからも分かるとおり React element はイミュータブルです。再描画する場合は新規 React element を構築して ReactDOM.render() に再度渡します。ReactDOM.render() は変更のあった仮想 DOM だけを更新します。このことを、以下のコードでは setInterval を利用して確認しています。

function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(
    element,
    document.getElementById('root')
  );
}
setInterval(tick, 1000);

React component から React element を返す

React element を返すものは React component とよばれます。React component は function として記述することもできますが、実際には後述の state などが利用できる class として記述されます。

function として定義された React component

function App(props) {
  return <h1>Hello, {props.name}</h1>;
}

ReactDOM.render(
  <App name="myname" />,
  document.getElementById('root')
);

class として定義された React component

class App extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

ReactDOM.render(
  <App name="myname" />,
  document.getElementById('root')
);

慣習として、アプリケーション全体を React で記述するときは App という名称のコンポーネントを root に render します。Rails テンプレートエンジン内で利用する場合などは複数箇所で render するためその限りではありません。

状態をもつ React component

class で記述した React component は、状態 statecomponentDidMount, componentWillUnmount といったライフサイクルフック持つことができます

class Clock extends React.Component {

  constructor(props) {
    super(props);
    // コンストラクタ内では setState を
    // 利用せず state に直接代入します。
    this.state = {
      date: new Date(),
      cnt: 0
    };
  }

  // render 後に実行されるライフサイクルフックです。
  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
    // 以下のようにしても同じ意味です。
    // const self = this;
    // this.timerID = setInterval(
    //   function(){ self.tick() },
    //   1000
    // );
  }

  // 仮想 DOM から本 component が削除される前に実行される
  // ライフサイクルフックです。
  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    // コンストラクタ外では setState を利用します。
    this.setState({
      date: new Date()
    });

    // setState は上記のオブジェクトを引数に指定するものと
    // 下記の関数を引数に指定するものがあります。
    this.setState((prevState, props) => {
      return {
        cnt: prevState.cnt + parseInt(props.incr)
      };
    });

    // 以下のようにしても同じ結果です。
    // this.setState(prevState => {
    //   return {
    //     cnt: prevState.cnt + 5
    //   };
    // });

    // 以下のようにしても同じ意味です。
    // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
    // this.setState((prevState, props) => ({
    //   cnt: prevState.cnt + parseInt(props.incr)
    // }));
    // this.setState(function(prevState, props){
    //   return {
    //     cnt: prevState.cnt + parseInt(props.incr)
    //   };
    // });
  }

  // React element を返します。
  render() {
    return (
      <div>
        <p>time: {this.state.date.toLocaleTimeString()}</p>
        <p>cnt: {this.state.cnt}</p>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock incr="5" />,
  document.getElementById('root')
);

イベントハンドリング

React におけるイベントハンドラは以下のように記述します。SyntheticEvent に記載のあるイベントをハンドリングできます。

class MyComponent extends React.Component {

  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // `handleClick` 内で `this` を利用するために忘れずに記述します。
    this.handleClick = this.handleClick.bind(this);
  }

  // `this` を利用するイベントハンドラ
  handleClick(e) {
    e.preventDefault(); // <a> タグの画面遷移を無効化したい場合
    this.setState(prevState => ({
      isToggleOn: !prevState.isToggleOn
    }));
  }

  // 引数のあるイベントハンドラ
  handleClickWithArgs(myarg, e) {
    console.log(myarg);
  }

  render() {
    return (
      <div>
        <a href="#" onClick={this.handleClick}>
          {this.state.isToggleOn ? 'ON' : 'OFF'}
        </a>
        <button onMouseOver={this.handleClickWithArgs.bind(this, 123)}>マウスオーバーでログ出力</button>
      </div>
    );
  }
}

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

条件分岐

if 文による分岐以外に、以下のような記法が利用できます

ReactDOM.render(
  <div>
    <h1>hello</h1>
    {true && <p>表示される</p>}
    {false && <p>表示されない</p>}
    {true ? <p>表示される</p> : <p>表示されない</p>}
  </div>,
  document.getElementById('root')
);

React component が React element として null を返すと何も表示されません。

function App(props) {
  if(!props.show) {
    return null;
  }
  return <h1>hello</h1>;
}

ReactDOM.render(
  <App show={false} />,
  document.getElementById('root')
);

複数の React elements を返す React component

React component は複数の React elements を返すことができますmap を利用すると便利です。

function ListItem(props) {
  // `map` 内ではないため `key` を指定する必要はありません。
  return <li>{props.value}</li>;
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map(number =>
    <ListItem
      key={number.toString()} // `map` 内で生成する要素には何らかの `key` を指定します。
      value={number} />  // `key` は props として渡されません。必要に応じて別途指定します。
  );
  const listItems2 = numbers.map((number, index) => // `index` も利用できます (非推奨)
    <ListItem
      key={index} // `key` は同じリスト `listItems2` 内でユニークであれば十分です。
      value={number} />
  );
  return (
    <div>
      <ul>{listItems}</ul>
      <ul>{listItems2}</ul>
    </div>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);

React component の階層化

様々な component の共通部分を汎用的な component として切り出して、それを複数 component の内部で利用することでコード全体のメンテナンス性が上がります。このような階層化された component 群に関するいくつかの有益なサンプルを示します。

複数 component で状態を共有

上位 component の state を下位 component に渡すことで、component 群が全体として持つ状態を統一できます。上位 component は下位 component にコールバック関数を合わせて渡しておき、下位 component 内で状態を更新する必要が発生した場合はそれを呼び出して上位 component の state を更新します。

class MyInnerComponent extends React.Component {

  constructor(props) {
    super(props);
    // 前述のとおり `handleChange` 内で `this` を利用するために忘れずに記述します。
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(e) {
    this.props.onMyChange(e.target.value); // input の値を取得してコールバック関数を実行します。
  }

  render() {
    return <input value={this.props.myval} onChange={this.handleChange} />;
  }
}

class MyComponent extends React.Component {

  constructor(props) {
    super(props);
    this.handleChange1 = this.handleChange1.bind(this); // 前述のとおり `handleChange1/2` 内で
    this.handleChange2 = this.handleChange2.bind(this); // `this` を利用するために忘れずに記述します。
    this.state = {myval: 0.0};
  }

  handleChange1(v) {
    const myval = parseFloat(v); // そのまま格納
    this.setState({myval: Number.isNaN(myval) ? 0.0 : (myval / 1.0)});
  }

  handleChange2(v) {
    const myval = parseFloat(v); // 半分にして格納
    this.setState({myval: Number.isNaN(myval) ? 0.0 : (myval / 2.0)});
  }

  render() {
    const myval = this.state.myval; // そのまま表示、二倍にして表示
    return (
      <div>
        <MyInnerComponent myval={myval * 1.0} onMyChange={this.handleChange1} />
        <MyInnerComponent myval={myval * 2.0} onMyChange={this.handleChange2} />
      </div>
    );
  }
}

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

React elements を props として渡す

props.children を利用すると以下のような React element の渡し方ができます。

function MyInnerComponent(props) {
  return (
    <div>
      {props.children}
    </div>
  );
}

function MyComponent() {
  return (
    <MyInnerComponent>
      <h1>hello</h1>
    </MyInnerComponent>
  );
}

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

複数の React element を渡す必要がある場合は、通常どおり属性値として渡します。

function MyInnerComponent(props) {
  return (
    <p>
      {props.p1}, {props.p2}
    </p>
  );
}

function MyComponent() {
  return (
    <MyInnerComponent
      p1={
        <span>Hello</span>
      }
      p2={
        <span>world!</span>
      }
    />
  );
}

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

form の制御

この続きが気になる方は
関連ページ
    概要 バージョン 1 の AngularJS で jQuery を内部的に利用できるのと同様に、こちらのページで基本的な使用方法を把握した React でも jQuery を内部的に利用した component を作成できます。本ページでは実際に jQuery プラグインをラップした簡単な React component のサンプルコードを示します。
    概要 React 16.8 で導入された Hook のサンプルコードを記載します。 コンポーネントの機能を共有するための手法であった Render Props や Higher-Order Components を利用する必要がなくなります。複数コンポーネントでの状態共有も簡単になります。 componentDidMount