ホームページ > 記事 > ウェブフロントエンド > 連続リクエストを処理する React フックを構築する方法
ユーザーのアクションに迅速に応答し、バックエンドから最新のデータをフェッチする必要がある場合、シーケンシャルなリクエストをサポートする React Hook が必要になる場合があります。このフックは、以前のリクエストがまだ進行中の場合はキャンセルし、最新のデータのみを返すことができます。これにより、パフォーマンスが向上するだけでなく、ユーザー エクスペリエンスも向上します。
簡単なシーケンシャルリクエスト React フックを構築することから始めましょう:
import { useCallback, useRef } from 'react'; const buildCancelableFetch = <T>( requestFn: (signal: AbortSignal) => Promise<T>, ) => { const abortController = new AbortController(); return { run: () => new Promise<T>((resolve, reject) => { if (abortController.signal.aborted) { reject(new Error('CanceledError')); return; } requestFn(abortController.signal).then(resolve, reject); }), cancel: () => { abortController.abort(); }, }; }; function useLatest<T>(value: T) { const ref = useRef(value); ref.current = value; return ref; } export function useSequentialRequest<T>( requestFn: (signal: AbortSignal) => Promise<T>, ) { const requestFnRef = useLatest(requestFn); const currentRequest = useRef<{ cancel: () => void } | null>(null); return useCallback(async () => { if (currentRequest.current) { currentRequest.current.cancel(); } const { run, cancel } = buildCancelableFetch(requestFnRef.current); currentRequest.current = { cancel }; return run().finally(() => { if (currentRequest.current?.cancel === cancel) { currentRequest.current = null; } }); }, [requestFnRef]); }
ここでの重要なアイデアは、記事「JavaScript で Promise を破棄する方法」から来ています。次のように使用できます:
import { useSequentialRequest } from './useSequentialRequest'; export function App() { const run = useSequentialRequest((signal: AbortSignal) => fetch('http://localhost:5000', { signal }).then((res) => res.text()), ); return <button onClick={run}>Run</button>; }
このようにすると、ボタンを素早く複数回クリックすると、最新のリクエストからのデータのみが取得され、以前のリクエストは破棄されます。
より包括的なシーケンシャル リクエスト React Hook が必要な場合、上記のコードには改善の余地があります。例:
実際に必要になるまで AbortController の作成を延期でき、不必要な作成コストを削減できます。
ジェネリックを使用して、あらゆるタイプのリクエスト引数をサポートできます。
更新バージョンは次のとおりです:
import { useCallback, useRef } from 'react'; function useLatest<T>(value: T) { const ref = useRef(value); ref.current = value; return ref; } export function useSequentialRequest<Args extends unknown[], Data>( requestFn: (signal: AbortSignal, ...args: Args) => Promise<Data>, ) { const requestFnRef = useLatest(requestFn); const running = useRef(false); const abortController = useRef<AbortController | null>(null); return useCallback( async (...args: Args) => { if (running.current) { abortController.current?.abort(); abortController.current = null; } running.current = true; const controller = abortController.current ?? new AbortController(); abortController.current = controller; return requestFnRef.current(controller.signal, ...args).finally(() => { if (controller === abortController.current) { running.current = false; } }); }, [requestFnRef], ); }
finally ブロックでは、競合状態を防ぐために、現在のコントローラーが abortController.current と等しいかどうかをチェックしていることに注意してください。これにより、アクティブなリクエストのみが実行状態を変更できるようになります。
より包括的な使用法:
import { useState } from 'react'; import { useSequentialRequest } from './useSequentialRequest'; export default function Home() { const [data, setData] = useState(''); const run = useSequentialRequest(async (signal: AbortSignal, query: string) => fetch(`/api/hello?query=${query}`, { signal }).then((res) => res.text()), ); const handleInput = async (queryStr: string) => { try { const res = await run(queryStr); setData(res); } catch { // ignore errors } }; return ( <> <input placeholder="Please input" onChange={(e) => { handleInput(e.target.value); }} /> <div>Response Data: {data}</div> </> ); }
オンラインで試すことができます。素早く入力すると、以前のリクエストはキャンセルされ、最新の応答のみが表示されます。
これが役立つと思われた場合は、 Web 開発に関するさらに役立つ記事やツールを入手するには、ニュースレターの購読をご検討ください 。読んでいただきありがとうございます!
以上が連続リクエストを処理する React フックを構築する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。