React自定义Hook调用报错「渲染的钩子比上一次多」问题求解
错误原因排查
你遇到的Error: Rendered more hooks than during the previous render报错,直接原因是你在按钮的onClick事件回调中调用了自定义Hook useRandomJoke,完全违反了React Hook的核心使用规则。
React Hook 核心使用规则
React官方明确规定了Hook的两条不可违反的使用规则:
- 只能在React函数组件的最顶层调用Hook,不可以在循环、条件判断、嵌套函数(比如事件回调、普通异步函数)中调用
- 只能在React函数组件、自定义Hook内部调用Hook,不可以在普通JavaScript函数中调用
规则背后的逻辑:React是严格按照Hook在每次渲染中的调用顺序,来对应维护每个Hook关联的状态的。如果某一次渲染时,Hook的调用数量、顺序和上一次渲染不一致,React就无法正确匹配状态,就会抛出你遇到的这个错误。你当前的写法只有点击按钮时才会触发useRandomJoke调用,前后两次渲染的Hook数量必然不一致,自然触发报错。
现有代码的问题拆解
除了Hook调用位置错误外,你的代码还有一个设计问题:
当前useRandomJoke的useEffect依赖项是空数组,只会在Hook挂载时请求一次笑话,本身就不支持点击刷新的需求。
修复方案
方案1:调整自定义Hook,暴露主动刷新方法
修改useRandomJoke代码
把请求逻辑抽离成可复用函数,和笑话内容一起返回给调用方:
import { useEffect, useState } from "react"; const useRandomJoke = () => { const [joke, setJoke] = useState(); // 抽离请求逻辑 const fetchJoke = async () => { const res = await fetch("https://api.icndb.com/jokes/random"); const data = await res.json(); setJoke(data.value.joke); }; // 组件挂载时默认请求一次 useEffect(() => { fetchJoke(); }, []); // 同时返回笑话内容和刷新方法 return [joke, fetchJoke]; }; export default useRandomJoke;
修改App组件代码
在组件顶层调用自定义Hook,点击时直接调用返回的刷新方法即可,不需要额外维护jokes状态:
import useRandomJoke from './useRandomJoke'; function App() { // 组件顶层调用Hook,符合调用规则 const [jokes, fetchJoke] = useRandomJoke(); return ( <div className="App"> <h1>随机笑话</h1> <p>{jokes}</p> {/* 点击直接触发刷新方法 */} <button onClick={fetchJoke}> 点击获取新笑话 </button> </div> ); } export default App;
方案2:给自定义Hook添加触发依赖项
如果你不想对外暴露请求方法,也可以通过外部依赖变化触发重新请求,同样符合Hook调用规则:
修改useRandomJoke代码
import { useEffect, useState } from "react"; // 接收外部传入的依赖项,依赖变化时自动重新请求 const useRandomJoke = (deps = []) => { const [joke, setJoke] = useState(); useEffect(() => { const fetchJoke = async () => { const res = await fetch("https://api.icndb.com/jokes/random"); const data = await res.json(); setJoke(data.value.joke); }; fetchJoke(); }, deps); return joke; }; export default useRandomJoke;
修改App组件代码
import { useState } from "react"; import useRandomJoke from './useRandomJoke'; function App() { // 用一个标志位控制刷新 const [refreshFlag, setRefreshFlag] = useState(0); // 顶层调用Hook,把标志位作为依赖传入 const jokes = useRandomJoke([refreshFlag]); return ( <div className="App"> <h1>随机笑话</h1> <p>{jokes}</p> {/* 点击修改标志位,触发Hook重新请求 */} <button onClick={() => setRefreshFlag(prev => prev + 1)}> 点击获取新笑话 </button> </div> ); } export default App;
内容的提问来源于stack exchange,提问作者Sana Momin
相关产品推荐
相关产品推荐

