React报错#310复盘小结
- 问题背景
- 解决方案
- 原理&学习
- 引发错误情况
- 1. 不要在循环,条件或嵌套函数中调用 Hook
- 2. 把所有的钩子移到组件的顶层,在任何可能返回值的条件之上。
- 总结
问题背景
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 进行关联。
const App = () => {
const [loading, setLoading] = useState(false)
useEffect(函数1)
if (条件) {
try {
…………
} catch {}
}
const [test, setTest] = useState('name')
}
If条件的存在可能导致触发catch,导致两种渲染情况不一致:
// 没进入到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>
);
}
总结
- 只从React函数组件或自定义钩子中调用Hook
- 只在最顶层使用 Hook
- 不要在循环,条件或嵌套函数中调用 Hook
- 确保总是在你的 React 函数的最顶层以及任何 return 之前使用 Hook
这有助于React在多个useState
和useEffect
调用之间保留钩子的状态。