axiosで非同期通信(React+TypeScript使用)

今まではjQueryでAjax(非同期通信)をしていましたが、 ReactやVueで非同期通信をする場合自分で組まないといけない状態です。

さすがに1からHTTPコマンドを叩いてくのは非効率なので、 axiosというPromiseベースのHTTPクライアントモジュールを使っていきます。

Node.jsのモジュールなのですが普通にブラウザでも使えるので、 ReactとVueで作るSPAにも採用される事が多く恐らくフロントエンドでは axiosがデファクトスタンダードになっているのではないでしょうか?

axiosの使い方はすごく簡単なので私も基本的にReact製のツールではよく使っています。

axios便利すぎて堕落しそう

今回はTypeScriptを使ってES6とES7での書き方をご紹介するので、 ご自分の環境に合わせておつかいください。

ネタバレするとES6で書いた方が扱いやすいです。

というかTypeScriptを使うと型が縛られるのでめっちゃ扱いが面倒になります(笑)

ですが扱えないわけでもないのでやっていきましょう。

axiosとTypeScriptだけに集中したいので、今回はcreate-react-app(ts版)を使います。

axiosとTypeScriptをインストールする

まずは土台を準備するためにcreate-react-appのTypeScript版を用意しましょう。

インストールが終わったらディレクトリを移動してaxiosを導入しましょう。

npx create-react-app axios-ts --typescript
cd axios-ts
yarn start

問題なくyarn start出来たらOKです。

次はaxiosの導入です。

yarn add axios

TypeScriptで型情報が必要になるので@types/axiosの導入も必要なんじゃないのかなって思って調べてみると、 どうやらaxiosだけで型情報も提供してくれてるようで入れる必要はなさそうでした。

導入に関してはこれでOKです。

まずはES6でaxiosを使って非同期通信を書いていきましょう。

axios自体がPromise(ES6で導入された非同期処理の書き方)を使っているので 必然的にES6になると思います。

Promiseが何かわからないという人はググってみてください。

わからなければ何をしているのかだけを理解すれば大丈夫です。

私もaxios使いまくってましたがPromiseについてはちゃんと理解してませんでした(笑)

使ってから覚えれば大丈夫です。

今回使うAPIサーバーについては各自ご用意ください。

探せば色々出てくると思います。

REST APIのモックが作れる 「JSON Serve」というものもあるので、 興味があったらローカルでAPIサーバーを立ててみるのが一番安心でしょう。

ES6でのaxiosの扱い方

それでは一番簡単なGETをやってみましょう。

POSTについては今回はやりません。

新たなReactコンポーネントを作ります。

srcの直下に「ApiGet.jsx」というファイルを作ってください。

TypeScriptにする前にまずはjavaScriptでの書き方を見てみましょう。

import axios from 'axios';

export const ApiGet_Simple = (URL) => {
    axios // axiosモジュールを使う
        .get(URL) // getメソッドを呼び出す
        .then((results) => { // レスポンスが来たらthenを実行
            console.log(results.data); // コンソールログにresultsに含まれるdataを表示
        })
        .catch((error) => { // 通信エラーが発生したら
            console.log('通信失敗'); // ログに失敗と表示
            console.log(error.status); // エラーコードを表示
        });
};

処理の説明はコメントに書きました。

恐らくこれが最もシンプルなaxiosの使い方じゃないでしょうか。

モジュールとして読み込めるので上記の書き方であれば「ApiGet_Simple("https://~~");」と書くと、 通信が始まりコンソールにログが表示されます。

成功すれば取得したjsonを表示して、ダメなら通信失敗と出てエラー内容がでます。

一旦使ってみましょう。

src直下にあるApp.tsxにて作成したaxiosモジュールを読み込んでみましょう。

import React from 'react';
import logo from './logo.svg';
import './App.css';

// 作ったaxiosモジュールの読み込み
import { ApiGet_Simple } from './ApiGet';

const App: React.FC = () => {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>

      { // 作成した関数を実行
        ApiGet_Simple("※※ココはアクセスしたらjsonで値を返してくれるAPIサーバーのアドレスを記述※※")
      }
    </div>
  );
}

export default App;

良い感じに通信できました。

ダメだった人はAPIサーバーの問題かと思われます。

それでは次にこれをTypeScriptで書いてみましょう。

import axios from 'axios';

export const ApiGet_Simple = (URL:string):void => {
    axios
        .get(URL)
        .then((results) => {
            console.log(results.data);
        })
        .catch((error) => {
            console.log('通信失敗');
            console.log(error.status);
        });
};

はい、これだけですね。 返り値が無いのでvoidになっています。

tsの設定次第では:voidを書かなくてもvoid扱いにしてくれていました。

これだとコンソールに出るだけなので実践で使えるように値を返してあげましょう。

import axios from 'axios';

export const ApiGet_Simple = (URL:string):any => {
    axios
        .get(URL)
        .then((results) => {
            console.log(results.data);
            return results.data;
        })
        .catch((error) => {
            console.log('通信失敗');
            console.log(error.status);
            return 
        });
};

axios自体の定義がanyになってるので返り値の型はanyでいけますが、 ちゃんと指定したい場合はgetの部分に取得するjsonのinterfaceを定義して指定します。

import axios from 'axios';

interface jsonType {
    id:number,
    data:string,
    flag:boolean
}

export const ApiGet_Simple = (URL:string):jsonType[] => {

    // エラー用に空データを準備
    let return_Json:jsonType[] = [];

    axios
        .get<jsonType[]>(URL)
        .then((results) => {
            return_Json = results.data;
            console.log(return_Json);
      // 成功したら取得できたデータを返す
            return return_Json;
        })
        .catch((error) => {
            console.log('通信失敗');
            console.log(error.status);
            // 失敗したときは空のjsonを返す
        });

  // エラーの場合はこれを返す
    return return_Json;
};

interface jsonTypeは仮のものです。

実際には返ってくるデータの形式にしてください。

interfaceを用意したら関数の引数の部分の後ろ「(URL:string):jsonType[]」と返り値となる型を記述します。

こうすると必ずjsonType[]で値を返さないといけなくなるので、 一旦空のjsonType型の空配列を作ります。

そしてaxiosを実行します。

ここで重要なのがget関数の部分ですね。

.get<型>(引数)

とやることで型指定できます。

無くても設定次第では大丈夫みたいですが、必要であれば入れてください。

そしてthenでは最初に作ったローカル変数に値を入れてもいいですし 入れずにそのままdataをreturnしてもよいです。

エラーでcatchに入った場合はそこで返さずaxios処理が終わってからreturnしないとエラーがでました。

余談ですが、厳密にやればcatchなんかもerrorなんかも型を付けてやらなきゃいかんのですが、 正直そこまでやってもあんまりメリットはないんじゃないかなって思いました。

TSの意味がないといったらそれまでですが、正直この辺りはTS縛りにすると無駄に時間がかかって 保守性はあがるのかもしれませんがかえって可読性も生産性も落ちるだけな気がしますね。

時には妥協も必要ということですね(笑)

ES6に関してはこれで大丈夫かと思います。

あくまで私個人の書き方なので、もっといい方法やスマートな方法があるかもしれません。

TypeScriptならココはこうしたほうが良いだろっていうのがあれば各自改良してください。

ES7でのaxiosの扱い方

そして問題となるES7の書き方なのですが、正直言ってあまりオススメしません。

書いてて逆に可読性落ちてんなって思いました。

使いこなせてないだけかも……

そしてcreate-react-appだとES7が理解できないので設定してやる必要があります。

なので今回はES7設定している人向けに書いてますのでご注意ください。

TypeScriptじゃなければよさそうですが型があるとすごく面倒でしたね。

色々試行錯誤して動いてくれたので、一応記述しておきます。

js版は記載しませんのでいきなりts版です。

import axios from 'axios';

interface jsonType {
    id:number,
    data:string,
    flag:boolean
}

// axiosでjson取得
export const ApiGet_Simple = (async (url:string) => {
  try {
    // 指定URLにGET
    const res = await axios.get<jsonType[]>(url);
    // (↑のawaitがついていると関数が実行完了するまで↓を動作しない仕組み)
    // 動作が完了して、リターンしてきたjsonを返す
    return res;
  } catch (err) {
    // 途中でエラーが出たら強制でエラーをスロー
    throw new Error(err.status);
  }
});

asyncとawaitはES7から追加された非同期処理の書き方です。

asyncとawaitの詳しい情報はググってください。

簡単に言うとPromiseをシンプルに書けるようになるといった感じですね。

関数の前にasyncを付けて待機したいところにawaitを付けるといった感じです。

awaitを付けないと処理が終わる前に次の処理にいってしまうので、 上記の場合だとgetが終わるまで次の処理にいかずに待ってくれます。

大本の処理自体は非同期なのでスクリプトが止まることもありません。

私自身がasync/awaitを完全に理解できてないので詳しい解説ができないので、 あまり鵜呑みにしないようにしてください(笑)

最後にasyncを使うと返り値がjsonTypeではなくPromise型のAxiosResponse型のjsonType[]となります。

なんかよくわかりませんね。

それでこれの実際のReactでの使い方が以下になります。

// まずは関数を実行してPromise型のjsonType配列を含むデータを取得
const stock_data:Promise<jsonType[]> = ApiGet_Simple("apiのURL").then(result => result.data);
// そこから中身を取り出す処理をしてreactのstateに格納(頑張れば1行で書けそう)
stock_data.then((result => this.setState({ stateVar: result })));

くっそめんどうですねこれは……

やってることを簡単に説明すると、作った関数はそのまま値を返すとPromise型なので扱いづらいので 中身を取り出してsetStateに配列データを格納するといった感じです。

もしES7で作ってて中身だけを取り出したいとなった人がいたら有効活用してください。

あくまでTSのエラーを回避した書き方になっているので、これが正しい書き方かどうかは保証いたしません。

まぁ書き方が1つじゃないというのがプログラミングなんですけどね……

もう一度結論を言っておくとES7でaxiosを使うと可読性と利便性が一気に落ちてしまうのでオススメ致しません。

私は勉強のためにES7で実装してみましたが今のところ問題なく理想の動きをしてくれています。

TypeScriptで型縛りについて

完全に余談になりますが、TSで色々記述をしていると回りくどい書き方になったり どうしても解決できないのでanyを使って妥協したりするともはやTypeScriptで書く必要があるのか疑問に思えてきます。

私自身が共同開発をしたことがないのであまり重要性を感じられないだけかもしれませんが、 JavaScriptの良いところを潰しまくってるような気がしますね。

正直プリミティブな部分だけ型を縛ってやればいいきがします。

JavaScriptの良さがかき消されまくってる……

数値か文字列か真偽値か配列かさえちゃんとしていればいいんじゃないですかね。

CやC++とか型が厳密な言語をやってる人からだとJavaScriptは気持ち悪いという人が多く 元々JavaScriptから入った私からするとそういう言語なんだよって思いますね。

違う言語で無理に自分が使ってた言語のルールを持ってくるのはなんか違う気がします。

まぁJavaScriptは拡張されまくったキメラ言語なんで正直規格をちゃんと決めて作り直してほしいですね。

結局ブラウザが理解できるようにトランスパイルかけるわけですから、 トランスパイルせずとも動くブラウザが普及してくれればいいのですが、世の中うまくいかないもんです。

それでは。