はじめてのReact #18「Ajaxでデータ取得」

昨今のWebアプリではWebAPI(RESTful API等)をAjaxなどで叩きデータを取得する、もしくはデータを保存するといった行為を日常的に行っているわけですが、今回はAjaxでデータ取得し表示するまでに挑戦してみます。

MonsterView Component

とあるRPGのモンスターの一覧が記録されたJSONファイルをAjaxで取得し、そのままReactで表示するサンプルです。公式ドキュメントの例をちょろっといじったものになります。

サンプル

データファイルです。実際にはサーバ側で動的に出力することが多いと思いますが、今回は静的なファイルで試します。ファイル名はdata.json

{
  "monsters": [
    { "id": 1, "name": "スライム", "hp": 10 },
    { "id": 2, "name": "ドラキー", "hp": 12 },
    { "id": 3, "name": "おおありくい", "hp": 36 }
  ] 
}

実際の処理を行うHTMLです。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <title>MonsterView</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 MonsterView extends React.Component {
  constructor(props) {
    super(props);
    this.state ={
      isLoaded: false,
      error: null,
      monsters: []
    }
  }

  componentDidMount() {
    fetch("https://s3-us-west-2.amazonaws.com/blog.katsubemakito.net/static/react1st/18/data.json")
      .then(res => res.json())
      .then(
        (result) => {
          this.setState({
            isLoaded: true,
            monsters: result.monsters
          });
        },
        (error) => {
          this.setState({
            isLoaded: true,
            error: error
          });
        }
      )
  }

  render() {
    if (this.state.error) {
      return <div>Error: {this.state.error.message}</div>;
    }
    else if ( !this.state.isLoaded ) {
      return <div>Loading...</div>;
    }
    else {
      return (
        <ul>
          {this.state.monsters.map( monster => (
            <li key={monster.id}>
              {monster.name} {monster.hp}
            </li>
          ))}
        </ul>
      );
    }
  }
}

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

実行結果

解説

いつ通信を行ってるの?

以前、Reactが用意している機能にLifeCycleについて取り上げましたが、今回はその中のcomponentDidMountを利用しています。componentDidMountメソッド内に処理を書いておくと、ComponentがDOMツリーに追加された直後(render()実行直後)に実行されます。

詳細は以前の記事を参照ください。 blog.katsubemakito.net

Fetch APIでJSON取得

componentDidMount内でやってることは非常にシンプルです。 詳細は端折りますが、FetchAPIはReactではなく比較的最近のJSで導入された機能になります。以前はXHR(XMLHttpRequest)を利用していたわけですが、その置き換えですね。Promise対応となりCallback地獄から開放された素敵機能です。

通信に成功しても失敗してもthis.setState()でステートを更新しています。ただし失敗した場合はステートのerrorにエラーメッセージが入ります。

    fetch("https://s3-us-west-2.amazonaws.com/blog.katsubemakito.net/static/react1st/18/data.json")
      .then(res => res.json())
      .then(
        (result) => {
          this.setState({
            isLoaded: true,
            monsters: result.monsters
          });
        },
        (error) => {
          this.setState({
            isLoaded: true,
            error: error
          });
        }
      )

FetchAPIについての詳細は以下を参照ください。 blog.katsubemakito.net

jQuery版

ちなみにReactはViewの機能しか提供されませんので、Ajax周りは他のライブラリと組み合わせることも可能です。例えばjQueryで実装する場合は以下のようになります。

headタグなどでjQueryを読み込みます。

  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

componentDidMountをjQueryの$.getJSONに置き換えてみます。$.ajaxでも大丈夫。

  componentDidMount() {
    let url = "https://s3-us-west-2.amazonaws.com/blog.katsubemakito.net/static/react1st/18/data.json";
    $.getJSON(url)
      .done((json)=>{
        this.setState({
            isLoaded: true,
            monsters: json.monsters
          });
      })
      .fail(()=>{
        this.setState({
            isLoaded: true,
            error: {message:`Can not fetch URL: ${url}`}
          });
      });
  }

FetchAPIとあまり変わらないですねw 以下のURLに実際に実行したファイルも置いておきます。 s3-us-west-2.amazonaws.com

状態に合わせて表示を切り替る

render()はステートの状態にあわせて返却する(表示する)内容を動的に変更しています、エラー時と通信の最中、そして通信に成功したときですね。

  render() {
    if (this.state.error) {
      return <div>Error: {this.state.error.message}</div>;
    }
    else if ( !this.state.isLoaded ) {
      return <div>Loading...</div>;
    }
    else {
      return (
        <ul>
          {this.state.monsters.map( monster => (
            <li key={monster.id}>
              {monster.name} {monster.hp}
            </li>
          ))}
        </ul>
      );
    }
  }

書籍

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

参考ページ

reactjs.org