Когда вам нужно быстро реагировать на действия пользователя и получать последние данные из бэкэнда, вам может потребоваться React Hook, поддерживающий последовательные запросы. Этот хук может отменить предыдущие запросы, если они все еще выполняются, и вернуть только самые последние данные. Это не только повышает производительность, но и упрощает работу пользователя.
Давайте начнем с создания простого хука React для последовательного запроса:
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]); }
Ключевая идея здесь взята из статьи «Как аннулировать обещания в 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], ); }
Обратите внимание, что в блоке Final мы проверяем, равен ли текущий контроллер 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}> ); }
Вы можете попробовать это онлайн: при быстром наборе текста предыдущие запросы будут отменены, и будет показан только последний ответ.
Если вы нашли это полезным, пожалуйста, рассмотрите возможность подписки на мою рассылку, чтобы получать больше полезных статей и инструментов о веб-разработке. Спасибо за прочтение!
Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.
Copyright© 2022 湘ICP备2022001581号-3