うーん、なんていうか…最近、[React]でデータ取ってくる時の話、よく考えるんだよね。fetch を使うべきか、Axios を使うべきか、みたいな。これ、もう何年も言われてる話だけど、意外と本質的なところを分かってないまま「なんとなくAxiosがいいらしい」って使ってる人、多いんじゃないかなって。
正直、僕も昔はそうだった。でも、色々プロジェクトを経験してくると、なんでこっちのほうが楽なのか、どういう時にハマるのかが、だんだん体でわかってくる。今日はそのへんの、ちょっと感覚的な部分も含めて、言葉にしてみようかなって思う。メモみたいなものだけど。
まず、結論から言うと…
先に言っちゃうと、ほとんどの [React] プロジェクトでは、もう考えずに Axios を使ったほうがいい。うん。特にチームで開発してるとか、ちょっと複雑なアプリケーション作るなら、もう圧倒的に。理由は単純で、開発者の「うっかりミス」とか「面倒くさい作業」を、ライブラリ側がうまいこと吸収してくれるから。精神衛生上、すごくいい。
fetch がダメなわけじゃないんだよ。あれはブラウザの標準機能だし、すごく低レベルで、ある意味ピュアな存在。でも、ピュアだからこそ、料理するのに一手間も二手間もかかる、みたいな。そういう感じ。
じゃあ、fetchの何がそんなに面倒なのか
よくある `fetch` のコードって、こんな感じだよね。多分、チュートリアルとかで最初に見るやつ。
useEffect(() => {
fetch("https://api.example.com/data")
.then((response) => {
// この一手間が、本当に忘れがち
if (!response.ok) {
throw new Error("ネットワークの応答が良くないです");
}
return response.json(); // そして、これも一手間
})
.then((data) => {
setData(data);
})
.catch((error) => {
setError(error.message);
});
}, []);
ぱっと見、問題なさそうに見える。でも、ここに落とし穴がいくつかあるんだよね。
- エラーハンドリングが直感的じゃない: `fetch` は、404 (Not Found) とか 500 (Internal Server Error) みたいなサーバーからのエラーを、`catch` ブロックに流してくれないんだ。あくまで「ネットワーク通信が失敗した」時だけ `catch` に行く。だから、`response.ok` が `false` かどうかを自分で毎回チェックしないと、エラー画面を出したつもりが、空のデータを処理しようとして別のエラーが出たりする。正直、これ、結構な落とし穴で。
- JSONへの変換が手動: レスポンスが来たら、まず `.json()` を呼び出して、JSON形式のボディをJavaScriptのオブジェクトに変換しないといけない。これも `Promise` を返すから、`.then` が一段階増える。大したことないように見えるけど、APIを叩くたびにこれを書くのは、地味に面倒くさい。
- タイムアウトがない: 標準でリクエストのタイムアウト機能がない。だから、サーバーの応答がめちゃくちゃ遅いと、ユーザーはずーっとローディング画面を見せられることになる。これも別途、自分で実装する必要がある。
- リクエストの中断が面倒: `useEffect` の中でAPIを叩く時、ユーザーがコンポーネントから離れた(アンマウントされた)後で `setData` が呼ばれると、「アンマウントされたコンポーネントのstateを更新しようとしました」みたいな警告が出る。これを防ぐには `AbortController` を使ってリクエストをキャンセルする必要があるんだけど、そのコードもまた、ちょっとごちゃごちゃするんだよね…。
要するに、`fetch` は「通信する」っていう最低限の機能しか提供してくれてなくて、アプリケーションで使う上で「当然あってほしい便利機能」は全部、自分で実装してね、っていうスタンスなんだ。
そこでAxios。どう楽になるか
同じことを Axios で書くと、こうなる。
useEffect(() => {
// AbortControllerを使ったキャンセル処理は後で説明するけど、一旦省略
axios.get("https://api.example.com/data")
.then((response) => {
// response.data にはもうJSオブジェクトが入ってる
setData(response.data);
})
.catch((error) => {
// 404や500エラーも、こっちに来る
setError(error.message);
});
}, []);
すっきりしたよね。さっきの面倒な点が、ほとんど解消されてる。
- エラーハンドリングが自然: 200番台以外のステータスコードは、全部 `catch` に流してくれる。これが本当に楽。開発者が期待する通りの動きをしてくれる。
- JSONの自動変換: レスポンスを受け取った時点で、`response.data` の中にはもうJavaScriptオブジェクトが入ってる。`.json()` を呼ぶ必要がない。
もっと言うと、 `async/await` を使うとさらに読みやすくなる。
const fetchData = async () => {
try {
const response = await axios.get("https://api.example.com/items");
setItems(response.data);
} catch (error) {
setError(error.message);
}
};
`.then` のネストから解放されて、上から下に処理が流れるように書ける。思考の負荷がぐっと下がる感じがする。
じゃあ、具体的に何が違うの?表で見てみようか
言葉で説明するより、並べてみたほうが分かりやすいかもしれない。
| 機能 | fetch API | Axios |
|---|---|---|
| JSONデータの扱い | 手動。`response.json()` を呼ぶ必要がある。一手間かかる。 | 自動で変換してくれる。`response.data` を見るだけ。楽。 |
| エラーハンドリング | ネットワークエラーのみ `catch` へ。4xx, 5xx系は自前で `response.ok` をチェックしないとダメ。忘れがち。 | 2xx系以外のステータスコードは全部 `catch` に流してくれる。直感的で安心。 |
| リクエストのキャンセル | 標準ではなし。`AbortController` を自分で組み合わせて実装する必要がある。ちょっと面倒。 | `CancelToken`(古い)か `AbortController` の `signal` を渡すだけで簡単にできる。 |
| タイムアウト設定 | これも標準ではなし。同じく `AbortController` と `setTimeout` を組み合わせるなど、工夫が必要。 | 設定オブジェクトに `timeout: 5000` みたいに書くだけ。すごく簡単。 |
| リクエスト/レスポンスの共通処理 | 自前でラッパー関数を作る必要がある。じゃないと毎回ヘッダーとか書く羽目になる。 | インターセプター (`interceptors`) っていう仕組みがある。全リクエストに認証トークンを付ける、とかがめちゃくちゃ楽。 |
| 依存関係 | なし。ブラウザ標準機能。これは強み。 | あり。`npm install axios` が必要。バンドルサイズが少しだけ増える。 |
もう一歩進んだ使い方:インターセプターとか
Axios が本当に輝くのは、アプリケーションが大きくなってきた時。特にこの「インターセプター」っていう機能が強力なんだよね。
これは、リクエストを送る「前」とか、レスポンスを受け取った「後」に、共通の処理を挟み込める機能。言葉だと分かりにくいかな。例えば、こんなことができる。
// 全てのリクエストヘッダーに、認証トークンを自動で付ける
axios.interceptors.request.use((config) => {
const token = localStorage.getItem("auth_token");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// レスポンスを共通でハンドリングする
axios.interceptors.response.use(
(response) => response, // 成功した場合はそのまま返す
(error) => {
// 例えば、認証エラー(401)が返ってきたら、自動でログイン画面に飛ばす
if (error.response && error.response.status === 401) {
window.location.href = '/login';
}
return Promise.reject(error); // それ以外のエラーは、呼び出し元のcatchに流す
}
);
これ、すごくない? APIを叩くコンポーネント側では、もう認証のこととか、共通のエラー処理のことを一切考えなくてよくなる。処理を1箇所にまとめられるから、コードは綺麗になるし、修正も楽になる。大規模なアプリだと、こういうのが本当に効いてくる。
あ、そうそう、大事なことを忘れてた。`useEffect` のクリーンアップ処理。さっき `fetch` のところで `AbortController` が面倒って話をしたけど、Axios でも `[React] 18` 以降は同じ `AbortController` を使うのが主流になった。書き方はこんな感じ。
useEffect(() => {
const controller = new AbortController();
axios.get("/posts", {
signal: controller.signal // AbortControllerのsignalを渡す
})
.then(res => setData(res.data))
.catch(err => {
if (axios.isCancel(err)) {
console.log("Request canceled", err.message);
} else {
// ... 通常のエラー処理
}
});
// コンポーネントがアンマウントされる時に、リクエストを中断する
return () => {
controller.abort();
};
}, []);
fetch とやってることは同じなんだけど、エラーハンドリングとか他の便利機能と組み合わせられるから、全体としてやっぱり Axios のほうが管理しやすいかな、という印象。
でも、Axiosが絶対じゃない場面もある
ここまで Axios を褒めてきたけど、じゃあ `fetch` はもういらないのかっていうと、そんなことはない。まあ、当たり前といえば当たり前なんだけど。
例えば、こういう場面なら `fetch` のほうがいいかも。
- 本当にごく簡単なGETリクエストしかしない場合: 例えば、ブログの記事を1個取ってくるだけ、みたいな。エラーハンドリングもそんなに凝らない、みたいな。
- 依存関係を極限まで減らしたい場合: ちょっとしたツールとか、ライブラリのサイズがすごくシビアな環境(例えば、Web Workerの中とか)で動かすスクリプトなら、わざわざ Axios を追加するまでもない。
- 最新のWeb標準に追従したいという強い意志がある場合: `fetch` は標準機能だから、進化していく。そういうのをキャッチアップしていくのが好き、という人なら。
Axiosの公式ドキュメント(これがグローバルな標準ですね)では、機能が網羅的に説明されているけど、日本の現場、たとえばQiitaとか見ていると、やっぱり「いかに楽に、安全に書くか」っていう文脈でAxiosが選ばれていることが多い気がする。エラーハンドリングをもうちょっと丁寧にやる文化があるというか。そういう意味で、日本の多くの[React]開発の現場では、Axiosのほうがチーム全体の生産性を上げやすいっていうのは、あると思う。
結局はトレードオフ。でも、ほとんどの「製品」開発においては、Axiosがもたらす開発体験の向上とか、コードの保守性の高さっていうメリットが、バンドルサイズが少し増えるっていうデメリットを上回ることが多いんじゃないかな。個人的にはそう思う。
反例と誤解
最後に、よくある誤解について少しだけ。
- 「Axiosは古くて、fetchが新しい」という誤解: `fetch` がブラウザ標準になったのは確かにここ数年の話だけど、Axios もちゃんとメンテナンスされてて、`AbortController` に対応したりと、モダンな開発に合わせて進化してる。どっちかが一方的に古い、っていうわけじゃない。
- 「Axiosを使えばセキュリティが上がる」という誤解: Axios は便利だけど、CSRF対策とかXSS対策とか、そういうセキュリティの問題を自動で解決してくれるわけじゃない。それはまた別のレイヤーの話で、開発者がちゃんと意識しないといけないこと。
- 「Next.jsとかのフレームワークでもAxios一択?」: これは少し違う。`Next.js` の App Router みたいに、フレームワーク自体が独自のデータフェッチ機構(`fetch`を拡張したもの)を提供している場合がある。そういう時は、まずフレームワークの推奨する方法に従うのが基本。キャッシュとか、サーバーコンポーネントとの連携とか、フレームワークの恩恵を最大限に受けられるから。その上で、クライアントコンポーネントでの複雑なやり取りのためにAxiosを併用する、っていうのが現実的な落とし所かな。
…と、まあ、つらつらと喋ってみたけど、だいたいそんな感じ。結局は、道具の特性をちゃんと理解して、適材適所で使い分けるのが一番大事ってことだね。でも、もし迷ったら、とりあえず Axios を選んでおけば、後で困ることは少ないと思う。
ところで、あなたは普段、fetchとAxios、どっちをよく使いますか? もし「こういう理由でこっち派」みたいなのがあったら、コメントで教えてくれると嬉しいです。
