當您需要快速回應使用者操作並從後端取得最新資料時,您可能需要一個支援順序請求的 React Hook。如果先前的請求仍在進行,則此掛鉤可以取消它們,並且僅返回最新的資料。這不僅提高了效能,還增強了使用者體驗。
讓我們從建立一個簡單的順序請求 React hook 開始:
import { useCallback, useRef } from 'react'; const buildCancelableFetch =( requestFn: (signal: AbortSignal) => Promise , ) => { const abortController = new AbortController(); return { run: () => new Promise ((resolve, reject) => { if (abortController.signal.aborted) { reject(new Error('CanceledError')); return; } requestFn(abortController.signal).then(resolve, reject); }), cancel: () => { abortController.abort(); }, }; }; function useLatest (value: T) { const ref = useRef(value); ref.current = value; return ref; } export function useSequentialRequest ( requestFn: (signal: AbortSignal) => Promise , ) { const requestFnRef = useLatest(requestFn); const currentRequest = useRef 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]); }
這裡的關鍵想法來自文章「How to Annul Promises in JavaScript」。你可以這樣使用它:
import { useSequentialRequest } from './useSequentialRequest'; export function App() { const run = useSequentialRequest((signal: AbortSignal) => fetch('http://localhost:5000', { signal }).then((res) => res.text()), ); return ; }
這樣,當您多次快速點擊按鈕時,您只會獲取最新請求的數據,先前的請求將被丟棄。
如果我們需要更全面的順序請求React Hook,上面的程式碼還有改進的空間。例如:
我們可以推遲創建 AbortController 直到實際需要時,減少不必要的創建成本。
我們可以使用泛型來支援任何類型的請求參數。
這是更新版本:
import { useCallback, useRef } from 'react'; function useLatest(value: T) { const ref = useRef(value); ref.current = value; return ref; } export function useSequentialRequest ( requestFn: (signal: AbortSignal, ...args: Args) => Promise, ) { const requestFnRef = useLatest(requestFn); const running = useRef(false); const abortController = useRef (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 ( { handleInput(e.target.value); }} />Response Data: {data}> ); }
您可以在線上嘗試:當您快速輸入時,先前的請求將被取消,只顯示最新的回應。
如果您覺得這有幫助, 請考慮訂閱 我的時事通訊,以獲取有關Web 開發的更多有用文章和工具。感謝您的閱讀!
免責聲明: 提供的所有資源部分來自互聯網,如果有侵犯您的版權或其他權益,請說明詳細緣由並提供版權或權益證明然後發到郵箱:[email protected] 我們會在第一時間內為您處理。
Copyright© 2022 湘ICP备2022001581号-3