React自定义Hook无法在函数内使用报错问题求助
问题原因分析
你在getTheData点击事件回调中调用useBored和useTranslate,直接违反了React Hook的核心规则:Hook必须在组件的顶层作用域调用,不能嵌套在事件处理函数、条件语句或循环中。React依赖Hook的调用顺序维护状态,嵌套调用会破坏这个顺序,导致状态管理混乱,从而触发违规报错。
解决方案
以下提供两种可行方案,根据你的需求选择:
方案1:改用普通异步函数(推荐,适配手动触发场景)
因为你的需求是点击卡片才发起请求,而非组件渲染时自动请求,用普通异步函数比Hook更贴合场景:
重构请求逻辑为普通函数
- 修改
useBored.ts为fetchBored.ts:export const fetchBoredActivity = async (type: string): Promise<string[]> => { const res = await fetch(`http://www.boredapi.com/api/activity?type=${type}`); const data = await res.json(); return [data.activity, data.link]; }; - 修改
useTranslate.ts为translateText.ts:export const translateText = async (text: string): Promise<string> => { const res = await fetch(`https://api.mymemory.translated.net/get?q=${text}&langpair=en|ar`); const data = await res.json(); return data.matches[0].translation; };
- 修改
更新CardComp组件逻辑
import React from "react"; import { fetchBoredActivity } from "../hooks/fetchBored"; import { translateText } from "../hooks/translateText"; interface ICard { placeholder: string; image: string; type: string; setD: React.Dispatch<React.SetStateAction<string>>; } const CardComp: React.FC<ICard> = ({ placeholder, image, type, setD }) => { const getTheData = async () => { try { // 先获取活动数据 const [activity] = await fetchBoredActivity(type); // 再翻译内容 const translatedText = await translateText(activity); // 更新父组件状态 setD(translatedText); } catch (error) { console.error("请求出错:", error); } }; return ( <div> <div onClick={getTheData} className="flex flex-col justify-center items-center p-7 cursor-pointer font-mono bg-[radial-gradient(ellipse_at_top_right,_var(--tw-gradient-stops))] from-blue-700 via-blue-800 to-gray-900" > <div className=" "> <img src={image} className="w-[2.3em]" /> </div> <h3>{placeholder}</h3> </div> </div> ); }; export default CardComp;
方案2:调整自定义Hook支持手动触发(保留Hook形式)
如果需要保留Hook的状态管理能力,可以给Hook添加触发控制,仅在手动触发时发起请求:
修改自定义Hook
- 更新
useBored.ts:import { useState, useEffect } from "react"; export const useBored = (type: string, trigger: boolean) => { const [data, setData] = useState<string[]>([]); const [isLoading, setIsLoading] = useState<boolean>(false); const [error, setError] = useState<string>(""); useEffect(() => { if (!trigger) return; const fetchData = async () => { setIsLoading(true); try { const res = await fetch(`http://www.boredapi.com/api/activity?type=${type}`); const result = await res.json(); setData([result.activity, result.link]); } catch (e) { setError((e as Error).message); } finally { setIsLoading(false); } }; fetchData(); }, [type, trigger]); return { data, isLoading, error }; }; - 更新
useTranslate.ts:import { useState, useEffect } from "react"; export const useTranslate = (text: string, trigger: boolean) => { const [data, setData] = useState<null | string>(null); const [isLoading, setIsLoading] = useState<boolean>(false); const [error, setError] = useState<string>(""); useEffect(() => { if (!trigger || !text) return; const fetchTranslation = async () => { setIsLoading(true); try { const res = await fetch(`https://api.mymemory.translated.net/get?q=${text}&langpair=en|ar`); const result = await res.json(); setData(result.matches[0].translation); } catch (e) { setError((e as Error).message); } finally { setIsLoading(false); } }; fetchTranslation(); }, [text, trigger]); return { data, isLoading, error }; };
- 更新
更新CardComp组件
import React, { useState, useEffect } from "react"; import { useBored } from "../hooks/useBored"; import { useTranslate } from "../hooks/useTranslate"; interface ICard { placeholder: string; image: string; type: string; setD: React.Dispatch<React.SetStateAction<string>>; } const CardComp: React.FC<ICard> = ({ placeholder, image, type, setD }) => { const [triggerFetch, setTriggerFetch] = useState(false); const { data: boredData } = useBored(type, triggerFetch); const { data: translatedData, isLoading: isTranslateLoading } = useTranslate(boredData[0] || "", triggerFetch); // 翻译完成后更新父组件状态并重置触发信号 useEffect(() => { if (!isTranslateLoading && translatedData) { setD(translatedData); setTriggerFetch(false); } }, [isTranslateLoading, translatedData, setD]); const getTheData = () => { setTriggerFetch(true); }; return ( <div> <div onClick={getTheData} className="flex flex-col justify-center items-center p-7 cursor-pointer font-mono bg-[radial-gradient(ellipse_at_top_right,_var(--tw-gradient-stops))] from-blue-700 via-blue-800 to-gray-900" > <div className=" "> <img src={image} className="w-[2.3em]" /> </div> <h3>{placeholder}</h3> </div> </div> ); }; export default CardComp;
内容的提问来源于stack exchange,提问作者Mustafa
相关产品推荐
相关产品推荐

