You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让React自定义useFetch钩子在相同URL时重新发起请求?

解决useFetch重复点击同一URL不发起请求的问题

下面提供几种比添加时间戳更优雅的解决方案:

方案一:添加手动触发依赖项

修改useFetch钩子,新增一个trigger参数作为依赖,每次需要刷新时更新这个参数即可强制发起请求:

export const useFetch = <T extends unknown>(
    url: string,
    options?: RequestInit,
    trigger?: number
) => {
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState(false);
    const [response, setResponse] = useState<T>();

    useEffect(() => {
        if (!url) return;
        const controller = new AbortController();
        setLoading(true);
        fetch(url, { ...options, signal: controller.signal })
            .then((res) => {
                setError(!res.ok);
                return res.json();
            })
            .then((json) => {
                setLoading(false);
                setResponse((json as unknown as JSONResponse).content as T);
            })
            .catch((err) => {
                // 忽略请求中止的错误
                if (err.name !== 'AbortError') {
                    console.log(err);
                    setLoading(false);
                    setError(true);
                }
            });

        return () => controller.abort();
    }, [url, options, trigger]);

    return { loading, error, response };
};

在组件中使用时,维护一个触发计数器,点击时更新计数器:

const [url, setUrl] = useState('');
const [fetchTrigger, setFetchTrigger] = useState(0);
const roomIDRef = useRef() as React.MutableRefObject<HTMLInputElement>;

const { error, response, loading } = useFetch<ExistsRoom>(url, undefined, fetchTrigger);

const joinRoom = () => {
    const id = roomIDRef.current.value;
    if (!id) return;
    setUrl(`http://localhost:8808/api/v1/room/exists?id=${id}`);
    // 更新触发值,强制useEffect重新执行
    setFetchTrigger(prev => prev + 1);
};

方案二:暴露手动刷新函数

在useFetch内部封装一个reload函数,通过useCallback保证引用稳定,外部直接调用即可发起请求:

export const useFetch = <T extends unknown>(
    url: string,
    options?: RequestInit
) => {
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState(false);
    const [response, setResponse] = useState<T>();
    // 用ref保存最新的options,避免依赖变化导致useCallback频繁更新
    const optionsRef = useRef(options);

    useEffect(() => {
        optionsRef.current = options;
    }, [options]);

    const fetchData = useCallback(async () => {
        if (!url) return;
        const controller = new AbortController();
        setLoading(true);
        try {
            const res = await fetch(url, { ...optionsRef.current, signal: controller.signal });
            setError(!res.ok);
            const json = await res.json();
            setResponse((json as unknown as JSONResponse).content as T);
        } catch (err) {
            if ((err as Error).name !== 'AbortError') {
                console.log(err);
                setError(true);
            }
        } finally {
            setLoading(false);
        }
        return () => controller.abort();
    }, [url]);

    // 初始加载
    useEffect(() => {
        fetchData();
    }, [fetchData]);

    return { loading, error, response, reload: fetchData };
};

组件中调用reload函数即可发起请求:

const [url, setUrl] = useState('');
const roomIDRef = useRef() as React.MutableRefObject<HTMLInputElement>;

const { error, response, loading, reload } = useFetch<ExistsRoom>(url);

const joinRoom = () => {
    const id = roomIDRef.current.value;
    if (!id) return;
    setUrl(`http://localhost:8808/api/v1/room/exists?id=${id}`);
    // 直接调用reload发起请求,不受url是否变化影响
    reload();
};

方案三:完全由外部控制请求时机

如果不需要钩子自动发起初始请求,只在用户操作时触发,可以调整钩子逻辑,将请求控制权完全交给外部:

export const useFetch = <T extends unknown>() => {
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState(false);
    const [response, setResponse] = useState<T>();

    const fetchData = useCallback(async (url: string, options?: RequestInit) => {
        const controller = new AbortController();
        setLoading(true);
        try {
            const res = await fetch(url, { ...options, signal: controller.signal });
            setError(!res.ok);
            const json = await res.json();
            setResponse((json as unknown as JSONResponse).content as T);
        } catch (err) {
            if ((err as Error).name !== 'AbortError') {
                console.log(err);
                setError(true);
            }
        } finally {
            setLoading(false);
        }
        return () => controller.abort();
    }, []);

    return { loading, error, response, fetchData };
};

组件中直接调用fetchData:

const roomIDRef = useRef() as React.MutableRefObject<HTMLInputElement>;
const { error, response, loading, fetchData } = useFetch<ExistsRoom>();

const joinRoom = () => {
    const id = roomIDRef.current.value;
    if (!id) return;
    const url = `http://localhost:8808/api/v1/room/exists?id=${id}`;
    // 每次点击都直接发起请求
    fetchData(url);
};

方案选择建议

  • 若需要保留钩子的自动加载能力,同时支持手动刷新,优先选方案二;
  • 若只需要简单的强制刷新触发,方案一实现成本最低;
  • 若不需要自动加载,完全由用户操作触发请求,选方案三更简洁。

内容的提问来源于stack exchange,提问作者DerPenz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 13:35:40