当你需要快速响应用户操作并从后端获取最新数据时,你可能需要一个支持顺序请求的 React Hook。如果之前的请求仍在进行,则此挂钩可以取消它们,并且仅返回最新的数据。这不仅提高了性能,还增强了用户体验。
让我们从构建一个简单的顺序请求 React hook 开始:
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 中取消 Promises”。你可以这样使用它:
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 Hook的详细内容。更多信息请关注PHP中文网其他相关文章!