Axiosは、JavaScriptアプリにAjaxリクエストを簡単に実装できる強力なHTTPクライアントです。 React here でAxiosを使用するための基本について説明したので、AxiosまたはAxios + Reactがまったく新しい場合は、最初にそれを読むことができます。

このチュートリアルでは、Axiosを使用してReactアプリ内にライブ検索機能を構築します。 私たちのアプリでは、themoviedb.orgのAPIを使用して簡単な映画検索を行うことができます。

このチュートリアルは3つのセクションに分かれています。

アプリの初期化

このチュートリアルは、Reactの使用経験があることを前提としているため、貴重な時間を節約するために初期化の手順をスキップします。 お気に入りのボイラープレートを使用できます。このチュートリアルでは、 Create ReactAppを使用してアプリを初期化します。

アプリが初期化されたら、axiosを追加しましょう。

$ yarn add axios or npm i axios

次に、以下のコードをAppコンポーネントにコピーします。

import React, { Component } from 'react';
import axios from 'axios';

import Movies from './Movies';

class App extends Component {
  state = {
    movies: null,
    loading: false,
    value: ''
  };

  search = async val => {
    this.setState({ loading: true });
    const res = await axios(
      `https://api.themoviedb.org/3/search/movie?query=${val}&api_key=dbc0a6d62448554c27b6167ef7dabb1b`
    );
    const movies = await res.data.results;

    this.setState({ movies, loading: false });
  };

  onChangeHandler = async e => {
    this.search(e.target.value);
    this.setState({ value: e.target.value });
  };

  get renderMovies() {
    let movies = <h1>There's no movies</h1>;
    if (this.state.movies) {
      movies = <Movies list={this.state.movies} />;
    }

    return movies;
  }

  render() {
    return (
      <div>
        <input
          value={this.state.value}
          onChange={e => this.onChangeHandler(e)}
          placeholder="Type something to search"
        />
        {this.renderMovies}
      </div>
    );
  }
}

export default App;

注:Moviesは単なるプレゼンテーション/ダムコンポーネントであり、提供するデータをレンダリングするだけです。 それは私たちのデータには影響しません。

入力コンポーネント

したがって、何かを入力するときにonChangeHandlerメソッドを呼び出す制御されたinput要素があります。 onChangeHandlerは、stateのvalueプロパティを変更し、searchメソッドを呼び出して、入力の値を引数として指定します。

上から次のコードを取り出します。

search = async val => {
  this.setState({ loading: true });
  const res = await axios(
    `https://api.themoviedb.org/3/search/movie?query=${val}&api_key=dbc0a6d62448554c27b6167ef7dabb1b`
  );
  const movies = await res.data.results;

  this.setState({ movies, loading: false });
};

searchメソッドでは、必要な映画を取得するためにAPIにGETリクエストを送信しています。 結果が得られたら、setStateを介してコンポーネントのstateを更新します。 また、setStateを使用して状態を変更すると、コンポーネントは変更された状態で再レンダリングされます。

そのような単純な!

不必要なリクエストの防止

入力を更新するたびにリクエストが送信されることに気付くかもしれません。 これは、特に大きな応答を受信する場合に、要求の過負荷につながる可能性があります。

この問題の動作を確認するには、ブラウザのDevToolsで[ネットワーク]タブを開きます。 [ネットワーク]タブをクリアします。 入力に映画の名前を入力します。

Too many requests, as can be seen from DevTools

ご覧のとおり、キーストロークが発生するたびにすべてのデータがダウンロードされます。 この問題を解決するために、srcディレクトリにutils.jsファイルを作成しましょう。

$ cd src
$ touch utils.js

次のコードをutils.jsにコピーします。

import axios from 'axios';

const makeRequestCreator = () => {
  let token;

  return (query) => {
    // Check if we made a request
    if(token){
      // Cancel the previous request before making a new request
      token.cancel()
    }
    // Create a new CancelToken
    token = axios.CancelToken.source()
    try{
      const res = await axios(query, {cancelToken: cancel.token})
      const result = data.data
      return result;
    } catch(error) {
        if(axios.isCancel(error)) {
          // Handle if request was cancelled
          console.log('Request canceled', error.message);
        } else {
          // Handle usual errors
          console.log('Something went wrong: ', error.message)
        }
    }
  }
}

export const search = makeRequestCreator()

Appコンポーネントも変更して、新しいユーティリティ関数を使用します。

// ...
import { search } from './utils'

class App extends Component {
  // ...

  search = async val => {
    this.setState({ loading: true });
    // const res = await axios(
    const res = await search(
      `https://api.themoviedb.org/3/search/movie?query=${val}&api_key=dbc0a6d62448554c27b6167ef7dabb1b`
    );
    const movies = res;

    this.setState({ movies, loading: false });
  };
  // ...

今何が起こっているのですか?

Axiosには、リクエストをキャンセルできるキャンセルトークンと呼ばれるものがあります。

makeRequestCreatorで、tokenという変数を作成します。 次に、リクエストを使用して、token変数が存在する場合、そのcancelメソッドを呼び出して、前のリクエストをキャンセルします。 次に、tokenに新しいCancelTokenを割り当てます。 その後、指定されたクエリを使用してリクエストを行い、結果を返します。

問題が発生した場合は、catchブロックでエラーを検出し、リクエストがキャンセルされたかどうかを確認して処理できます。

今すぐネットワークタブで何が起こるか見てみましょう:

Screenshot: much better performance!

ご覧のとおり、ダウンロードした応答は1つだけです。 現在、ユーザーは使用したものに対してのみ料金を支払います。

HTTPリクエストとレスポンスのキャッシュ

入力に同じテキストを複数回入力すると、そのたびに新しいリクエストが作成されます。

これを修正しましょう。 utils.jsのユーティリティ関数を少し変更します。

const resources = {};

const makeRequestCreator = () => {
  let cancel;

  return async query => {
    if (cancel) {
      // Cancel the previous request before making a new request
      cancel.cancel();
    }
    // Create a new CancelToken
    cancel = axios.CancelToken.source();
    try {
      if (resources[query]) {
        // Return result if it exists
        return resources[query];
      }
      const res = await axios(query, { cancelToken: cancel.token });

      const result = res.data.results;
      // Store response
      resources[query] = result;

      return result;
    } catch (error) {
      if (axios.isCancel(error)) {
        // Handle if request was cancelled
        console.log('Request canceled', error.message);
      } else {
        // Handle usual errors
        console.log('Something went wrong: ', error.message);
      }
    }
  };
};

export const search = makeRequestCreator()

ここでは、ダウンロードした応答を保持するresources定数を作成しました。 また、新しいリクエストを実行するときは、最初にresourcesオブジェクトにこのクエリの結果があるかどうかを確認します。 含まれている場合は、その結果を返します。 適切な結果が得られない場合は、新しいリクエストを作成し、結果をresourcesに保存します。 簡単です!

すべてを簡単にまとめましょう。inputに何かを入力するたびに:

  • 以前のリクエストがある場合はキャンセルします。
  • 入力した結果の以前の結果がすでにある場合は、新しいリクエストを行わずにそれを返すだけです。
  • その結果が得られない場合は、新しい結果を作成して保存します。

興味のある方は、このアプリのReduxバージョンを thisrepoで見つけることができます。

結論

おめでとうございます🎉🎉🎉! 不要な応答をダウンロードせず、応答をキャッシュするライブ検索機能を構築しました。 Axiosの助けを借りてReactで効率的なライブ検索機能を構築する方法について1つか2つ学んだことを願っています。

現在、ユーザーはできるだけ少ないトラフィックデータを使用しています。 このチュートリアルを楽しんだら、共有してください! 😄

この投稿の最終結果とソースコードは、 thisCodeSandboxにあります。