- React.js で API エンドポイントにアクセスしてリソースを取得したい!
バックエンドサーバーにリソースを取得しにいく方法、最初のうちは結構戸惑うことも多いと思います。
この記事ではaxiosを使ってリソースを取ってくる方法をご紹介します。
React.js と Axios を使って API アクセスする方法
早速、具体的な方法をお伝えします。
- APIアクセス用インスタンスを作成
- 各エンドポイントにアクセスするコードを作成
- ②を書くコンポーネントで呼び出す
それぞれ解説していきます。
エンドポイントへのアクセス用インスタンスを作成
API サーバーへのアクセスを担当するモジュールを作成しましょう。
ファイル名はutils/api.ts
として、axios.create()
メソッドで API アクセス用のインスタンスを作ります。
import axios from 'axios';
const api = axios.create({
baseURL: 'http://localhost:8000/api/',
timeout: 1000,
});
export default api;
ここでエンドポイントの URL を定義しておくことで、エンドポイントが変更になったときはここだけ直せば良いことになります。
各エンドポイントにアクセスするためのモジュールを作成
上記のapi.ts
で作成したインスタンスに対して、HTTP リクエストを実行するロジックを書いていきましょう。
import api from './api';
interface Matching {
id?: number;
[key: string]: any; // 他のプロパティを含む可能性があるため
}
export const fetchMatchings = async (): Promise<Matching[]> => {
try {
const response = await api.get('matchings/');
return response.data;
} catch (error) {
console.error('Error fetching matchings:', error);
throw error;
}
};
export const createMatching = async (matchingData: Matching): Promise<Matching> => {
try {
const response = await api.post('matchings/', matchingData);
return response.data;
} catch (error) {
console.error('Error creating matching:', error);
throw error;
}
};
export const updateMatching = async (id: number, matchingData: Matching): Promise<Matching> => {
try {
const response = await api.put(`matchings/${id}/`, matchingData);
return response.data;
} catch (error) {
console.error('Error updating matching:', error);
throw error;
}
};
export const deleteMatching = async (id: number): Promise<void> => {
try {
await api.delete(`matchings/${id}/`);
} catch (error) {
console.error('Error deleting matching:', error);
throw error;
}
};
このように、エンドポイントの種類ごとに一つのモジュールに集約すると便利です。
各コンポーネントで呼び出す
matchingService.ts
で定義したAPIアクセスを各コンポーネントから呼び出しましょう。
少し長いですが、結論となるコードを掲載します。
import React, { useEffect, useState } from 'react';
import { fetchMatchings, createMatching, updateMatching, deleteMatching } from './matchingService';
interface Matching {
id: number;
name: string;
// その他のプロパティを追加
}
const MatchingsList: React.FC = () => {
const [matchings, setMatchings] = useState<Matching[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const loadMatchings = async () => {
try {
const data = await fetchMatchings();
setMatchings(data);
} catch (error) {
setError('Failed to fetch matchings');
} finally {
setLoading(false);
}
};
loadMatchings();
}, []);
const handleCreate = async (newMatching: Matching) => {
try {
const createdMatching = await createMatching(newMatching);
setMatchings([...matchings, createdMatching]);
} catch (error) {
console.error('Error creating matching:', error);
}
};
const handleUpdate = async (id: number, updatedMatching: Matching) => {
try {
const updated = await updateMatching(id, updatedMatching);
setMatchings(matchings.map(m => (m.id === id ? updated : m)));
} catch (error) {
console.error('Error updating matching:', error);
}
};
const handleDelete = async (id: number) => {
try {
await deleteMatching(id);
setMatchings(matchings.filter(m => m.id !== id));
} catch (error) {
console.error('Error deleting matching:', error);
}
};
if (loading) return <div>Loading...</div>;
if (error) return <div>{error}</div>;
return (
<div>
<h1>Matchings List</h1>
<ul>
{matchings.map(matching => (
<li key={matching.id}>
{matching.name}
<button onClick={() => handleUpdate(matching.id, { ...matching, name: 'Updated Name' })}>Update</button>
<button onClick={() => handleDelete(matching.id)}>Delete</button>
</li>
))}
</ul>
<button onClick={() => handleCreate({ id: 0, name: 'New Matching' })}>Add New</button>
</div>
);
};
export default MatchingsList;
いきなりコードを見ても理解しづらいので、ステップに分けて詳細に解説していきます。
APIモジュールのインポート
すでに作成したmatchingService.ts
をインポートします。
import { fetchMatchings, createMatching, updateMatching, deleteMatching } from './matchingService';
このコードでmatchingService.ts
の中にある以下の関数が使えるようになりました。
fetchMatchings
createMatching
updateMatching
deleteMatching
データの取得
今回はコンポーネントが画面に描画されると同時に、API からデータ取得したいですよね。その場合には、画面表示というメインの機能の他に API アクセスといういわば「副作用」をつけたい。
このように副作用をつけたい場合にはuseEffect
フックを利用します。(詳しくは後述)
useEffect(() => {
const loadMatchings = async () => {
try {
const data = await fetchMatchings();
setMatchings(data);
} catch (error) {
setError('Failed to fetch matchings');
} finally {
setLoading(false);
}
};
loadMatchings();
}, []);
useEffect
の第二引数として空の配列[]
を指定しているので、このコンポーネントが呼び出された瞬間に一度だけ API リクエストが実行されることになります。
データの一時保存場所
ここで、API から取得したデータを一時的に保存しておくためにsetMatchings
が必要になってきます。
それを定義しているのが以下のuseState
フックを用いた行です。(詳しくは後述)
const [matchings, setMatchings] = useState<Matching[]>([]);
ここでは簡単に説明します。matchings
はデータの一時保存場所を表し、setMatchings
は一時保存場所に格納するための関数になっています。
つまりmatchings
に値を格納する場合には、以下のように書きます。
setMatchings(data);
このコードは一つ前のuseEffect
フック内で書いたコードでしたね。
API アクセスのためのコードを作り込んでいく
handleCreate
では、非同期関数を定義しています。新しいマッチングオブジェクトを作成して、現在のマッチングリストに追加する処理を行っています。
const handleCreate = async (newMatching: Matching) => {
try {
const createdMatching = await createMatching(newMatching);
setMatchings([...matchings, createdMatching]);
} catch (error) {
console.error('Error creating matching:', error);
}
};
ちょっと分解しつつ、解説してきます。
const handleCreate = async (newMatching: Matching) => {
まずはhandleCreate
関数を宣言します。async
としているので非同期処理が行われることになります。
引数はMatching
型のnewMatching
という引数を定義しました。
try {
// ここに非同期処理を記述
} catch (error) {
console.error('Error creating matching:', error);
}
try
ブロック内で非同期処理を行って、エラーが発生したらcatch
ブロックで処理されることになります。
const createdMatching = await createMatching(newMatching);
先に定義しておいたcreateMatching
関数を呼び出していますね。await
を使っているので非同期処理で関数の結果が返ってくるまで待ち、結果が返ってきたらcreatedMatching
に格納しています。
setMatchings([...matchings, createdMatching]);
最後に状態の更新を行なっています。
setMatchings
は、useState
フックで定義された状態更新関数ですね。
...
はスプレッド構文といって、現在のmatchings
状態を一度展開しています。さらに新しく作成されたcreateMatching
をその末尾に追加した新しい配列を作成します。
これにより、matchings
状態が更新されて、React がコンポーネントを際レンダリングしても新しいマッチングが表示されるようになります。
コードを理解するための補足情報
useState
useState
フックを使うとコンポーネント内で状態(state)を管理できるようになります。
const [matchings, setMatchings] = useState<Matching[]>([]);
上記の例ではmatchings
を状態変数と呼び、setMatchings
が状態を更新するための関数ということになります。
基本的な構文は次のとおりです。
const [state, setState] = useState(initialState);
state
: 現在の状態の値setState
: 状態を更新するための関数initialState
: 状態の初期値
これを踏まえて、実際のコードを読み込んでいきましょう。
const [matchings, setMatchings] = useState<Matching[]>([]);
matchings
は状態変数、setMatchings
は状態を更新するための関数ですね。
問題は=
以下のuseState
フック以降です。
まず()
の中に空の配列が入っているので、初期値は[]
となります。
残るは<Matching[]>
の部分になります。
これは一言でいうと「Matching
型の要素を持つ配列」であることを表していて、このような指定方法をジェネリクスと呼ばれます。
Matching
型は、以下のように定義されます。
interface Matching {
id?: number;
[key: string]: any; // 他のプロパティを含む可能性があるため
}
id
はnumber
型であることが定義されています。さらに、?
とあることからid
はあってもなくても良い任意のプロパティであることがわかります。
[key: string]: any
は「インデックスシグネチャ」と呼ばれていて、任意の数のプロパティを持っていて、キーは文字列型、バリューはどんな型でも良いというany
が指定されています。
useEffect
フックとは?
useEffect
を利用すると、ページをロードした後の特定のタイミングで指定した処理を登録することができます。
このように画面描画以外に実行したい処理のことを「副作用(サイドエフェクト)」と呼びます。具体的には以下のような副作用が登録可能です。
- API からデータを取得
- タイマーを設定
- DOM を直接操作
以下は、useEffect
の基本的な使い方です。
import React, { useEffect } from 'react';
const ExampleComponent = () => {
useEffect(() => {
// ここに副作用を記述します。
console.log('Component mounted or updated');
// オプション: クリーンアップ関数を返す
return () => {
console.log('Component will unmount or update');
};
}, []); // 依存配列が空なので、この副作用はコンポーネントのマウント時にのみ実行されます。
return <div>Example</div>;
};
この副作用を「いつ」発動させるかについては、useEffect
の第二引数の配列(依存配列)で制御します。
次の二パターンを覚えておきましょう。
- 空配列
[]
: 初回マウント時に一度だけ実行 - 値入り配列
[value1, value2]
:値の変更のたびに実行
なお、マウントとは「コンポーネントが最初に画面表示される瞬間」のことで、さらに具体的には「コンポーネントが最初にDOMに挿入される瞬間」とも言えます。
以下は、useEffect
を使ってコンポーネントがマウントされたときにデータを取得する例です。
import React, { useEffect, useState } from 'react';
const DataFetchingComponent = () => {
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
const response = await fetch('https://api.example.com/data');
const result = await response.json();
setData(result);
};
fetchData();
}, []); // 空の依存配列なので、この副作用はマウント時に一度だけ実行される
if (!data) {
return <div>Loading...</div>;
}
return (
<div>
<h1>Data</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
};
export default DataFetchingComponent;
コメント