React报错#310复盘小结+hooks使用的场景+调用原理

文章详细阐述了React中遇到的#310错误,即“Renderedmorehooksthanduringthepreviousrender”。问题源于在循环、条件或嵌套函数中不正确地调用Hook。解决方案是确保Hook始终在组件的顶层调用,避免在条件语句中使用。文章还解释了React依赖于Hook调用的顺序来管理内部状态,并提供了修改代码的示例以遵循Hook规则。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题背景

apm报错:Minified React error #310
https://reactjs.org/docs/error-decoder.html/?invariant=310
[图片]

当我们有条件地调用一个钩子或在所有钩子运行之前提前返回时,会产生"Rendered more hooks than during the previous render"错误。

解决方案

const App = () => {
    …………
+   const [loading, setLoading] = useState(false)
+   useEffect(函数1, [……])
    
    if (条件) {
      try {
        …………
      } catch {}
    }
   
-   const [loading, setLoading] = useState(false)
-   useEffect(函数1, [……])
    return (……)
}

if中 catch {} 阻塞了后续hook的渲染,为了解决该错误,将所有的钩子移到函数组件的顶层,以及不要在条件中使用钩子。

原理&学习

React 靠的是 Hook 调用的顺序
在正常的程序中,Hook 的调用顺序在每次渲染中都是相同的

const App = () => {
    const [loading, setLoading] = useState(false)
    useEffect(函数1)
    const [test, setTest] = useState('name')
}
// 首次渲染
useState('false')            // 1. 使用 false 初始化变量名为 loading 的 state
useEffect(函数1)             // 2. 添加 effect 以保存 form 操作
useState('name')            // 3. 使用 'Poppins' 初始化变量名为 surname 的 state

// 二次渲染
useState('false')            // 1. 读取变量名为 loading 的 state(参数被忽略)
useEffect(函数1)             // 2. 替换保存 form 的 effect
useState('name')            // 3. 读取变量名为 surname 的 state(参数被忽略)

只要 Hook 的调用顺序在多次渲染之间保持一致,React 就能正确地将内部 state 和对应的 Hook 进行关联。

那如果在正常程序中插入一个if语句呢?如下:

const App = () => {
    const [loading, setLoading] = useState(false)
    useEffect(函数1)
    if (条件) {
      return ()
    }
    const [test, setTest] = useState('name')
}

If条件的存在导致程序提前终止,不再执行下方语句(而下方又存在一些hook),导致两种渲染情况不一致:

// 没进入到catch
useState('false')            // 1. 使用 false 初始化变量名为 loading 的 state
useEffect(函数1)             // 2. 添加 effect 以保存 form 操作
useState('name')            // 3. 使用 'Poppins' 初始化变量名为 surname 的 state

// 进入到catch中
useState('false')             // 1. 读取变量名为 loading 的 state(参数被忽略)
useEffect(函数1)              // 2. 替换保存 form 的 effect
//useState('name')           // 3. 此 Hook 被忽略!

引发错误情况

https://zh-hans.reactjs.org/docs/hooks-rules.html

1. 不要在循环,条件或嵌套函数中调用 Hook

export default function App() {
  const [counter, setCounter] = useState(0);
-  if (counter > 0) {
-    useEffect(() => {
-      console.log(counter);
-    });
-  }
// 将if条件语句移到useEffect钩子内部
+  useEffect(() => {
+    if (counter > 0) {
+      console.log(counter);
+    }
+  });
  return (
    ……
}

2. 把所有的钩子移到组件的顶层,在任何可能返回值的条件之上。

export default function App() {
  const [counter, setCounter] = useState(0);
+ const [color, setColor] = useState('salmon');

  if (counter > 0) {
    return <h2>Returning early</h2>;
  }
  // Error: 该hook在counter<=0条件时,才被调用
- const [color, setColor] = useState('salmon');
  
  return (
    <div><button onClick={() => setCounter(counter + 1)}>toggle loading</button><h1>Hello world</h1></div>
  );
}

3. React函数组件或自定义钩子中只在调用Hook

// 在普通函数中不要使用hook
const AppContent = () => {
  const [counter, setCounter] = useState(0);

  return (
    …………
  );
}
export const App = {
  dataIndex: 'app',
- render: AppContent,
+ render: () => <AppContent />
}

总结

  • 只从React函数组件或自定义钩子中调用Hook
  • 只在最顶层使用 Hook
  • 不要在循环,条件或嵌套函数中调用 Hook
  • 确保总是在你的 React 函数的最顶层以及任何 return 之前使用 Hook

这有助于React在多个useStateuseEffect调用之间保留钩子的状态。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你脸上有BUG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值