ReactのuseEffectフックを使用してWebAPIを呼び出す方法
著者は、 Creative Commons を選択して、 Write forDOnationsプログラムの一環として寄付を受け取りました。
序章
React 開発では、Web アプリケーションプログラミングインターフェイス(API)は、シングルページアプリケーション(SPA)設計の不可欠な部分です。 APIは、アプリケーションがプログラムでサーバーと通信してユーザーにリアルタイムデータを提供し、ユーザーの変更を保存するための主要な方法です。 Reactアプリケーションでは、APIを使用して、ユーザー設定の読み込み、ユーザー情報の表示、構成またはセキュリティ情報の取得、およびアプリケーションの状態変更の保存を行います。
このチュートリアルでは、useEffectおよびuseStateフックを使用して、テスト目的のローカルAPIとして JSONサーバーを使用し、サンプルアプリケーションで情報を取得して表示します。 コンポーネントが最初にマウントされたときに情報をロードし、APIを使用して顧客の入力を保存します。 また、ユーザーが変更を加えたときにデータを更新し、コンポーネントがアンマウントされたときにAPIリクエストを無視する方法を学びます。 このチュートリアルを終了するまでに、ReactアプリケーションをさまざまなAPIに接続し、リアルタイムデータを送受信できるようになります。
前提条件
- Node.jsを実行する開発環境が必要になります。 このチュートリアルは、Node.jsバージョン10.22.0およびnpmバージョン6.14.6でテストされました。 これをmacOSまたはUbuntu18.04にインストールするには、Node.jsをインストールしてmacOSにローカル開発環境を作成する方法またはのPPAを使用したインストール]セクションの手順に従います。 Ubuntu18.04にNode.jsをインストールする方法。
- Create React App でセットアップされたReact開発環境で、不要なボイラープレートが削除されています。 これを設定するには、ステップ1 —Reactクラスコンポーネントの状態を管理する方法のチュートリアルの空のプロジェクトを作成します。 このチュートリアルでは、プロジェクト名として
api-tutorialを使用します。 - このチュートリアルでは、
useStateおよびuseEffectフックを含むReactコンポーネントとフックを使用します。 コンポーネントとフックについては、チュートリアルReactコンポーネントのフックで状態を管理する方法とReactで非同期データの読み込み、遅延読み込み、コード分割を処理する方法で学ぶことができます。 - また、JavaScriptとHTMLの基本的な知識も必要です。これは、HTMLシリーズでWebサイトを構築する方法およびJavaScriptでコーディングする方法にあります。 CSSの基本的な知識も役立ちます。これは、 Mozilla DeveloperNetworkで見つけることができます。
ステップ1—プロジェクトとローカルAPIを作成する
このステップでは、テストデータソースとして使用する JSONサーバーを使用して、ローカルの RESTAPIを作成します。 後で、食料品のリストを表示し、リストにアイテムを追加するアプリケーションを作成します。 JSONサーバーがローカルAPIになり、GETおよびPOSTリクエストを行うためのライブURLを提供します。 ローカルAPIを使用すると、自分または別のチームがライブAPIを開発している間に、コンポーネントのプロトタイプを作成してテストする機会があります。
このステップを完了すると、Reactアプリケーションに接続できるローカルのモックAPIを作成できるようになります。
多くのアジャイルチームでは、フロントエンドチームとAPIチームが並行して問題に取り組んでいます。 リモートAPIの開発中にフロントエンドアプリケーションを開発するために、完全なリモートAPIを待っている間に使用できるローカルバージョンを作成できます。
模擬ローカルAPIを作成する方法はたくさんあります。 Node または別の言語を使用して、単純なサーバーを作成できますが、最も簡単な方法は、JSONサーバーのNodeパッケージを使用することです。 このプロジェクトは、JSONファイルからローカルRESTAPIを作成します。
開始するには、json-serverをインストールします。
npm install --save-dev json-server
インストールが完了すると、成功メッセージが表示されます。
Output+ [email protected] added 108 packages from 40 contributors and audited 1723 packages in 14.505s 73 packages are looking for funding run `npm fund` for details found 0 vulnerabilities
json-serverは、JavaScriptオブジェクトに基づいてAPIを作成します。 キーはURLパスであり、値は応答として返されます。 JavaScriptオブジェクトをローカルに保存し、ソース管理にコミットします。
アプリケーションのルートにあるdb.jsonというファイルを開きます。 これは、APIから要求する情報を格納するJSONになります。
nano db.json
listのキーを持つオブジェクトと、idおよびitemのキーを持つ値の配列を追加します。 これにより、食料品リストのアイテムが一覧表示されます。 キーlistは、最終的に/listのエンドポイントを持つURLを提供します。
api-tutorial / db.json
{
"list": [
{ "id": 1, "item": "bread" },
{ "id": 2, "item": "grapes" }
]
}
このスニペットでは、食料品リストの開始点としてbreadとgrapesをハードコーディングしています。
ファイルを保存して閉じます。 APIサーバーを実行するには、コマンドラインからjson-serverを使用し、API構成ファイルへの引数を指定します。 package.jsonにスクリプトとして追加します。
package.jsonを開きます:
nano package.json
次に、APIを実行するためのスクリプトを追加します。 さらに、delayプロパティを追加します。 これにより、応答が抑制され、APIリクエストとAPIレスポンスの間に遅延が生じます。 これにより、サーバーの応答を待機しているときにアプリケーションがどのように動作するかについての洞察が得られます。 1500ミリ秒のdelayを追加します。 最後に、-pオプションを使用してポート3333でAPIを実行し、create-react-app実行スクリプトと競合しないようにします。
api-tutorial / package.json
{
"name": "do-14-api",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.3"
},
"scripts": {
"api": "json-server db.json -p 3333 --delay 1500",
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"json-server": "^0.16.1"
}
}
ファイルを保存して閉じます。 新しいターミナルまたはタブで、次のコマンドを使用してAPIサーバーを起動します。
npm run api
チュートリアルの残りの間、これを実行し続けます。
コマンドを実行すると、APIリソースを一覧表示する出力が表示されます。
Output> json-server db.json -p 3333
\{^_^}/ hi!
Loading db.json
Done
Resources
http://localhost:3333/list
Home
http://localhost:3333
Type s + enter at any time to create a snapshot of the database
http:// localhost:3333 / list を開くと、ライブAPIが見つかります。
ブラウザでエンドポイントを開くときは、GETメソッドを使用しています。 ただし、json-serverはGETメソッドに限定されません。 他の多くのRESTメソッドも実行できます。 たとえば、POSTの新しいアイテムを作成できます。 新しいターミナルウィンドウまたはタブで、curlからPOSTを使用して、タイプがapplication/jsonの新しいアイテムを作成します。
curl -d '{"item":"rice"}' -H 'Content-Type: application/json' -X POST http://localhost:3333/list
送信する前に、コンテンツを文字列化する必要があることに注意してください。 curlコマンドを実行すると、成功メッセージが表示されます。
Output{
"item": "rice",
"id": 3
}
ブラウザを更新すると、新しいアイテムが表示されます。
POSTリクエストは、db.jsonファイルも更新します。 アプリケーションで作業するときに、構造化されていないコンテンツや役に立たないコンテンツを誤って保存することへの障壁がないため、変更に注意してください。 バージョン管理にコミットする前に、必ず変更を確認してください。
このステップでは、ローカルAPIを作成しました。 デフォルト値を使用して静的ファイルを作成する方法と、GETやPOSTなどのRESTfulアクションを使用してそれらの値をフェッチまたは更新する方法を学習しました。 次のステップでは、APIからデータをフェッチし、アプリケーションに表示するサービスを作成します。
ステップ2—useEffectを使用してAPIからデータを取得する
このステップでは、useEffectフックを使用して食料品のリストを取得します。 別のディレクトリでAPIを使用するサービスを作成し、Reactコンポーネントでそのサービスを呼び出します。 サービスを呼び出した後、useStateフックを使用してデータを保存し、結果をコンポーネントに表示します。
この手順を完了すると、FetchメソッドとuseEffectフックを使用してWebAPIを呼び出すことができるようになります。 結果を保存して表示することもできます。
APIが機能するようになったので、データとコンポーネントをフェッチして情報を表示するサービスが必要です。 サービスを作成することから始めます。 Reactコンポーネント内で直接データをフェッチできますが、データ取得機能を表示コンポーネントから分離しておくと、プロジェクトの参照と更新が簡単になります。 これにより、コンポーネント間でメソッドを再利用したり、テストをモックインしたり、エンドポイントが変更されたときにURLを更新したりできます。
srcディレクトリ内にservicesというディレクトリを作成します。
mkdir src/services
次に、テキストエディタでlist.jsというファイルを開きます。
nano src/services/list.js
このファイルは、/listエンドポイントでのすべてのアクションに使用します。 fetch関数を使用してデータを取得する関数を追加します。
api-tutorial / src / services / list
export function getList() {
return fetch('http://localhost:3333/list')
.then(data => data.json())
}
この関数の唯一の目的は、データにアクセスし、data.json()メソッドを使用して応答をJSONに変換することです。 GETがデフォルトのアクションであるため、他のパラメーターは必要ありません。
fetchに加えて、 Axios などの一般的なライブラリがあり、直感的なインターフェイスを提供し、デフォルトのヘッダーを追加したり、サービスで他のアクションを実行したりできます。 ただし、fetchはほとんどのリクエストで機能します。
ファイルを保存して閉じます。 次に、App.cssを開き、最小限のスタイルを追加します。
nano src/components/App/App.css
CSSを次のように置き換えて、少量のパディングを含むwrapperのクラスを追加します。
api-tutorial / src / components / App / App.css
.wrapper {
padding: 15px;
}
ファイルを保存して閉じます。 次に、データを取得してアプリケーションに表示するためのコードを追加する必要があります。
App.jsを開きます:
nano src/components/App/App.js
機能コンポーネントでは、useEffectフックを使用して、コンポーネントのロード時または一部の情報の変更時にデータをフェッチします。 useEffectフックの詳細については、 React を使用した非同期データの読み込み、遅延読み込み、コード分割の処理方法をご覧ください。 また、useStateフックを使用して結果を保存する必要があります。
useEffectとuseStateをインポートし、listという変数とsetListというセッターを作成して、useStateフック:
api-tutorial / src / components / App / App.js
import React, { useEffect, useState } from 'react';
import './App.css';
function App() {
const [list, setList] = useState([]);
return(
<>
</>
)
}
export default App;
次に、サービスをインポートしてから、useEffectフック内でサービスを呼び出します。 コンポーネントがマウントされている場合は、listをsetListに更新します。 データを設定する前にコンポーネントがマウントされているかどうかを確認する必要がある理由を理解するには、非同期データの読み込み、遅延読み込み、Reactによるコード分割の処理方法のステップ2 —マウントされていないコンポーネントのエラーの防止を参照してください。 。
現在、ページの読み込み時にエフェクトを実行しているのは1回だけなので、依存関係の配列は空になります。 次のステップでは、さまざまなページアクションに基づいてエフェクトをトリガーし、常に最新の情報を入手できるようにします。
次の強調表示されたコードを追加します。
api-tutorial / src / components / App / App.js
import React, { useEffect, useState } from 'react';
import './App.css';
import { getList } from '../../services/list';
function App() {
const [list, setList] = useState([]);
useEffect(() => {
let mounted = true;
getList()
.then(items => {
if(mounted) {
setList(items)
}
})
return () => mounted = false;
}, [])
return(
<>
</>
)
}
export default App;
最後に、 .map を使用してアイテムをループし、リストに表示します。
api-tutorial / src / components / App / App
import React, { useEffect, useState } from 'react';
import './App.css';
import { getList } from '../../services/list';
function App() {
const [list, setList] = useState([]);
useEffect(() => {
let mounted = true;
getList()
.then(items => {
if(mounted) {
setList(items)
}
})
return () => mounted = false;
}, [])
return(
<div className="wrapper">
<h1>My Grocery List</h1>
<ul>
{list.map(item => <li key={item.item}>{item.item}</li>)}
</ul>
</div>
)
}
export default App;
ファイルを保存して閉じます。 これを行うと、ブラウザが更新され、アイテムのリストが表示されます。
このステップでは、APIからデータを取得するサービスを設定します。 useEffectフックを使用してサービスを呼び出す方法と、ページにデータを設定する方法を学習しました。 JSX内にもデータを表示しました。
次のステップでは、POSTを使用してAPIにデータを送信し、その応答を使用して、アクションが成功したことをユーザーに警告します。
ステップ3—APIにデータを送信する
このステップでは、FetchAPIとPOSTメソッドを使用してデータをAPIに送り返します。 Webフォームを使用してonSubmitイベントハンドラーでデータを送信し、アクションが完了すると成功メッセージを表示するコンポーネントを作成します。
この手順を完了すると、APIに情報を送信できるようになり、リクエストが解決したときにユーザーにアラートを送信できるようになります。
サービスへのデータの送信
食料品のリストを表示するアプリケーションがありますが、コンテンツも保存できない限り、あまり便利な食料品アプリではありません。 APIにPOST新しいアイテムを追加するサービスを作成する必要があります。
src/services/list.jsを開きます:
nano src/services/list.js
ファイル内に、itemを引数として取り、POSTメソッドを使用してデータをAPIに送信する関数を追加します。 以前と同様に、FetchAPIを使用できます。 今回は、より多くの情報が必要になります。 2番目の引数としてオプションのオブジェクトを追加します。 メソッド— POST —をヘッダーとともに含めて、Content-Typeをapplication/jsonに設定します。 最後に、bodyで新しいオブジェクトを送信します。 必ずJSON.stringifyを使用してオブジェクトを文字列に変換してください。
応答を受け取ったら、値をJSONに変換します。
tutorial / src / services / list.js
export function getList() {
return fetch('http://localhost:3333/list')
.then(data => data.json())
}
export function setItem(item) {
return fetch('http://localhost:3333/list', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ item })
})
.then(data => data.json())
}
ファイルを保存して閉じます。
注:実稼働アプリケーションでは、エラー処理とチェックを追加する必要があります。 たとえば、エンドポイントのスペルを間違えた場合でも、404応答を受け取り、data.json()メソッドは空のオブジェクトを返します。 この問題を解決するには、応答をJSONに変換する代わりに、data.okプロパティを確認します。 偽の場合は、エラーをスローしてから、コンポーネントで.catchメソッドを使用して、ユーザーに失敗メッセージを表示することができます。
サービスを作成したので、コンポーネント内でサービスを利用する必要があります。
App.jsを開きます:
nano src/components/App/App.js
inputと送信buttonを囲むform要素を追加します。
api-tutorial / src / components / App / App.js
import React, { useEffect, useState } from 'react';
import './App.css';
import { getList } from '../../services/list';
function App() {
const [list, setList] = useState([]);
useEffect(() => {
let mounted = true;
getList()
.then(items => {
if(mounted) {
setList(items)
}
})
return () => mounted = false;
}, [])
return(
<div className="wrapper">
<h1>My Grocery List</h1>
<ul>
{list.map(item => <li key={item.item}>{item.item}</li>)}
</ul>
<form>
<label>
<p>New Item</p>
<input type="text" />
</label>
<button type="submit">Submit</button>
</form>
</div>
)
}
export default App;
スクリーンリーダーでフォームにアクセスできるように、必ずinputをlabelで囲んでください。 type="submit"をbuttonに追加して、フォームを送信する役割があることを明確にすることもお勧めします。
ファイルを保存します。 これを行うと、ブラウザが更新され、フォームが見つかります。
次に、inputを制御コンポーネントに変換します。 ユーザーが新しいリストアイテムを正常に送信した後にフィールドをクリアできるように、制御されたコンポーネントが必要になります。
まず、useStateフックを使用して、入力情報を保持および設定する新しい状態ハンドラーを作成します。
api-tutorial / src / components / App / App.js
import React, { useEffect, useState } from 'react';
import './App.css';
import { getList } from '../../services/list';
function App() {
const [itemInput, setItemInput] = useState('');
const [list, setList] = useState([]);
useEffect(() => {
let mounted = true;
getList()
.then(items => {
if(mounted) {
setList(items)
}
})
return () => mounted = false;
}, [])
return(
<div className="wrapper">
<h1>My Grocery List</h1>
<ul>
{list.map(item => <li key={item.item}>{item.item}</li>)}
</ul>
<form>
<label>
<p>New Item</p>
<input type="text" onChange={event => setItemInput(event.target.value)} value={itemInput} />
</label>
<button type="submit">Submit</button>
</form>
</div>
)
}
export default App;
状態ハンドラーを作成したら、inputの値をitemInputに設定し、onChangeイベントハンドラー。
これで、ユーザーは新しいリストアイテムをフォームに入力できます。 次に、フォームをサービスに接続します。
handleSubmitという関数を作成します。 handleSubmitは引数としてイベントを受け取り、event.preventDefault()を呼び出して、フォームによるブラウザーの更新を停止します。
サービスからsetItemをインポートし、handleSubmit関数内のitemInput値を使用してsetItemを呼び出します。 handleSubmitをonSubmitイベントハンドラーに渡してフォームに接続します。
api-tutorial / src / components / App / App.js
import React, { useEffect, useState } from 'react';
import './App.css';
import { getList, setItem } from '../../services/list';
function App() {
const [itemInput, setItemInput] = useState('');
const [list, setList] = useState([]);
useEffect(() => {
let mounted = true;
getList()
.then(items => {
if(mounted) {
setList(items)
}
})
return () => mounted = false;
}, [])
const handleSubmit = (e) => {
e.preventDefault();
setItem(itemInput)
};
return(
<div className="wrapper">
<h1>My Grocery List</h1>
<ul>
{list.map(item => <li key={item.item}>{item.item}</li>)}
</ul>
<form onSubmit={handleSubmit}>
<label>
<p>New Item</p>
<input type="text" onChange={event => setItemInput(event.target.value)} value={itemInput} />
</label>
<button type="submit">Submit</button>
</form>
</div>
)
}
export default App;
ファイルを保存します。 そうすると、値を送信できるようになります。 [ネットワーク]タブに正常な応答が表示されることに注意してください。 しかし、リストは更新されず、入力はクリアされません。
成功メッセージの表示
アクションが成功したことをユーザーに示すことは、常に良い習慣です。 そうしないと、ユーザーは値を複数回再送信しようとしたり、アクションが失敗したと思ってアプリケーションを終了したりする可能性があります。
これを行うには、useStateを使用してステートフル変数とセッター関数を作成し、ユーザーにアラートメッセージを表示するかどうかを示します。 alertがtrueの場合、<h2>タグにSubmitSuccessfulというメッセージを表示します。
setItem promise が解決したら、入力をクリアしてアラートメッセージを設定します。
api-tutorial / src / components / App / App.js
import React, { useEffect, useState } from 'react';
import './App.css';
import { getList, setItem } from '../../services/list';
function App() {
const [alert, setAlert] = useState(false);
const [itemInput, setItemInput] = useState('');
const [list, setList] = useState([]);
useEffect(() => {
let mounted = true;
getList()
.then(items => {
if(mounted) {
setList(items)
}
})
return () => mounted = false;
}, [])
const handleSubmit = (e) => {
e.preventDefault();
setItem(itemInput)
.then(() => {
setItemInput('');
setAlert(true);
})
};
return(
<div className="wrapper">
<h1>My Grocery List</h1>
<ul>
{list.map(item => <li key={item.item}>{item.item}</li>)}
</ul>
{alert && <h2> Submit Successful</h2>}
<form onSubmit={handleSubmit}>
<label>
<p>New Item</p>
<input type="text" onChange={event => setItemInput(event.target.value)} value={itemInput} />
</label>
<button type="submit">Submit</button>
</form>
</div>
)
}
export default App;
ファイルを保存します。 これを行うと、ページが更新され、APIリクエストが解決された後に成功メッセージが表示されます。
追加できる最適化は他にもたくさんあります。 たとえば、アクティブなリクエストがある間はフォーム入力を無効にしたい場合があります。 フォーム要素の無効化について詳しくは、Reactでフォームを作成する方法をご覧ください。
これで、結果が成功したことをユーザーに警告しましたが、警告メッセージは消えず、リストは更新されません。 これを修正するには、アラートを非表示にすることから始めます。 この場合、1秒などの短い期間の後に情報を非表示にする必要があります。 setTimeout関数を使用してsetAlert(false)を呼び出すことができますが、すべてのコンポーネントレンダリングで実行されないように、useEffectでラップする必要があります。
App.js内で新しいエフェクトを作成し、alertをトリガーの配列に渡します。 これにより、alertが変更されるたびにエフェクトが実行されます。 これは、alertがfalseからtrueに変更された場合に実行されますが、alertがtrueから[ X138X]。 アラートが表示された場合にのみ非表示にしたいので、alertがtrueの場合にのみ、setTimeoutを実行する条件をエフェクト内に追加します。
api-tutorial / src / components / App / App.js
import React, { useEffect, useState } from 'react';
import './App.css';
import { getList, setItem } from '../../services/list';
function App() {
const [alert, setAlert] = useState(false);
const [itemInput, setItemInput] = useState('');
const [list, setList] = useState([]);
...
useEffect(() => {
if(alert) {
setTimeout(() => {
setAlert(false);
}, 1000)
}
}, [alert])
const handleSubmit = (e) => {
e.preventDefault();
setItem(itemInput)
.then(() => {
setItemInput('');
setAlert(true);
})
};
return(
<div className="wrapper">
...
</div>
)
}
export default App;
1000ミリ秒後にsetTimeout関数を実行して、ユーザーが変更を読み取る時間を確保します。
ファイルを保存します。 これで、alertが変更されるたびに実行されるエフェクトができました。 アクティブなアラートがある場合、1秒後にアラートを閉じるタイムアウト機能を開始します。
取得したデータを更新する
次に、古いデータのリストを更新する方法が必要です。 これを行うには、useEffectフックに新しいトリガーを追加して、getList要求を再実行します。 最も関連性の高いデータを確保するには、リモートデータに変更があったときにいつでも更新されるトリガーが必要です。 幸い、alert状態を再利用して、ユーザーがデータを更新するたびに実行されることがわかっているため、別のデータ更新をトリガーできます。 以前と同様に、アラートメッセージが消えるときを含め、alertが変更されるたびにエフェクトが実行されるように計画する必要があります。
今回は、ページの読み込み時にエフェクトもデータをフェッチする必要があります。 list.lengthが真実であり、alertがfalseであり、すでに更新されていることを示す場合、データフェッチの前に関数を終了する条件を作成します。データ。 必ずalertとlistをトリガーの配列に追加してください。
import React, { useEffect, useState } from 'react';
import './App.css';
import { getList, setItem } from '../../services/list';
function App() {
const [alert, setAlert] = useState(false);
const [itemInput, setItemInput] = useState('');
const [list, setList] = useState([]);
useEffect(() => {
let mounted = true;
if(list.length && !alert) {
return;
}
getList()
.then(items => {
if(mounted) {
setList(items)
}
})
return () => mounted = false;
}, [alert, list])
...
return(
<div className="wrapper">
...
</div>
)
}
export default App;
ファイルを保存します。 これを行うと、新しいアイテムを送信した後にデータが更新されます。
この場合、alertはlistの状態に直接関係していません。 ただし、古いデータを無効にするイベントと同時に発生するため、これを使用してデータを更新できます。
マウントされていないコンポーネントの更新の防止
考慮する必要がある最後の問題は、マウントされていないコンポーネントに状態を設定しないようにすることです。 データをフェッチする効果のlet mounted = trueの問題に対する解決策がありますが、効果ではないため、handleSubmitでは解決策が機能しません。 アンマウント時に値をfalseに設定する関数を返すことはできません。 さらに、すべての関数に同じチェックを追加するのは非効率的です。
この問題を解決するには、mountedをuseEffectフックから持ち上げてコンポーネントのレベルに保持することにより、複数の機能で使用される共有変数を作成できます。 この関数を使用して、useEffectの最後で値をfalseに設定します。
App.js内で、関数の先頭でmountedを宣言します。 次に、他の非同期機能でデータを設定する前に、コンポーネントがマウントされているかどうかを確認してください。 useEffect関数内のmounted宣言を必ず削除してください。
api-tutorial / src / components / App / App.js
import React, { useEffect, useState } from 'react';
import './App.css';
import { getList, setItem } from '../../services/list';
function App() {
const [alert, setAlert] = useState(false);
const [itemInput, setItemInput] = useState('');
const [list, setList] = useState([]);
let mounted = true;
useEffect(() => {
if(list.length && !alert) {
return;
}
getList()
.then(items => {
if(mounted) {
setList(items)
}
})
return () => mounted = false;
}, [alert, list])
useEffect(() => {
if(alert) {
setTimeout(() => {
if(mounted) {
setAlert(false);
}
}, 1000)
}
}, [alert])
const handleSubmit = (e) => {
e.preventDefault();
setItem(itemInput)
.then(() => {
if(mounted) {
setItemInput('');
setAlert(true);
}
})
};
return(
<div className="wrapper">
...
</div>
)
}
export default App;
変更を加えると、Reactアプリを実行しているターミナルでエラーが発生します。
ErrorAssignments to the 'mounted' variable from inside React Hook useEffect will be lost after each render. To preserve the value over time, store it in a useRef Hook and keep the mutable value in the '.current' property. Otherwise, you can move this variable directly inside useEffect react-hooks/exhaustive-deps
Reactは、変数が安定していないことを警告しています。 再レンダリングがあるときはいつでも、変数を再計算します。 通常、これにより最新の情報が保証されます。 この場合、永続化するためにその変数に依存しています。
解決策は、useRefと呼ばれる別のフックです。 useRefフックは、コンポーネントの存続期間中、変数を保持します。 唯一の秘訣は、.currentプロパティを使用するために必要な値を取得することです。
App.js内で、useRefフックを使用してmountedを参照に変換します。 次に、mountedの各使用法をmounted.currentに変換します。
api-tutorial / src / components / App / App.js
import React, { useEffect, useRef, useState } from 'react';
import './App.css';
import { getList, setItem } from '../../services/list';
function App() {
const [alert, setAlert] = useState(false);
const [itemInput, setItemInput] = useState('');
const [list, setList] = useState([]);
const mounted = useRef(true);
useEffect(() => {
mounted.current = true;
if(list.length && !alert) {
return;
}
getList()
.then(items => {
if(mounted.current) {
setList(items)
}
})
return () => mounted.current = false;
}, [alert, list])
useEffect(() => {
if(alert) {
setTimeout(() => {
if(mounted.current) {
setAlert(false);
}
}, 1000)
}
}, [alert])
const handleSubmit = (e) => {
e.preventDefault();
setItem(itemInput)
.then(() => {
if(mounted.current) {
setItemInput('');
setAlert(true);
}
})
};
return(
<div className="wrapper">
...
</div>
)
}
export default App;
また、useEffectのクリーンアップ機能で変数を設定する場合は注意が必要です。 クリーンアップ機能は、エフェクトが再実行される前に常に実行されます。 つまり、alertまたはlistが変更されるたびに、クリーンアップ機能() => mounted.current = falseが実行されます。 誤った結果を避けるために、エフェクトの開始時に必ずmounted.currentをtrueに更新してください。 そうすれば、コンポーネントがアンマウントされたときにのみfalseに設定されることを確認できます。
ファイルを保存して閉じます。 ブラウザが更新されると、新しいリストアイテムを保存できるようになります。
注:誤ってAPIを複数回再実行することは一般的な問題です。 コンポーネントを削除してから再マウントするたびに、元のデータフェッチをすべて再実行します。 これを回避するには、特にデータ量が多いまたは遅いAPIのキャッシュ方法を検討してください。 サービス呼び出しのメモ化から、サービスワーカーとのキャッシュ、カスタムフックまで、あらゆるものを使用できます。 useSWRやreactquery など、サービス呼び出しをキャッシュするための一般的なカスタムフックがいくつかあります。
どのアプローチを使用する場合でも、最新のデータをフェッチしたい場合があるため、キャッシュを無効にする方法を必ず検討してください。
このステップでは、APIにデータを送信しました。 データが送信されたときにユーザーを更新する方法と、リストデータの更新をトリガーする方法を学習しました。 また、useRefフックを使用してコンポーネントのステータスを保存し、複数のサービスで使用できるようにすることで、マウントされていないコンポーネントにデータを設定することを回避しました。
結論
APIを使用すると、多くの便利なサービスに接続できます。 ユーザーがブラウザを閉じたり、アプリケーションの使用を停止したりした後でも、データを保存および取得できます。 適切に編成されたコードを使用すると、サービスをコンポーネントから分離できるため、コンポーネントは、データの発信元を知らなくてもデータのレンダリングに集中できます。 Web APIは、ブラウザセッションまたはストレージの機能をはるかに超えてアプリケーションを拡張します。 彼らはあなたのアプリケーションをウェブ技術の全世界に開放します。
Reactチュートリアルをもっと読みたい場合は、 Reactトピックページを確認するか、React.jsシリーズのコーディング方法ページに戻ってください。