著者は、 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+ json-server@0.16.1 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シリーズのコーディング方法ページに戻ってください。