开发者问题收集

未知的错误:渲染的钩子少于预期。这可能是由于React Hooks中的意外提早回报声明引起的

2018-11-25
166812

给定以下组件,当我按下年龄选择器并将值更改为 15 时,我会呈现一个没有驾驶执照字段的表单,但会出现错误:

Uncaught Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement.
    at invariant (react-dom.development.js:55)
    at finishHooks (react-dom.development.js:11581)
    at updateFunctionComponent (react-dom.development.js:14262)
    at beginWork (react-dom.development.js:15103)
    at performUnitOfWork (react-dom.development.js:17817)
    at workLoop (react-dom.development.js:17857)
    at HTMLUnknownElement.callCallback (react-dom.development.js:149)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:199)
    at invokeGuardedCallback (react-dom.development.js:256)
    at replayUnitOfWork (react-dom.development.js:17113)
    at renderRoot (react-dom.development.js:17957)
    at performWorkOnRoot (react-dom.development.js:18808)
    at performWork (react-dom.development.js:18716)
    at flushInteractiveUpdates$1 (react-dom.development.js:18987)
    at batchedUpdates (react-dom.development.js:2210)
    at dispatchEvent (react-dom.development.js:4946)
    at interactiveUpdates$1 (react-dom.development.js:18974)
    at interactiveUpdates (react-dom.development.js:2217)
    at dispatchInteractiveEvent (react-dom.development.js:4923)

以下示例代码:

const {useState} = React;

function App() {
  const [name, setName] = useState('Mary');
  const [age, setAge] = useState(16);

  if (age < 16) {
    return (
      <div>
        Name:{' '}
        <input
          value={name}
          onChange={e => {
            setName(e.target.value);
          }}
        />
        <br />
        Age:{' '}
        <input
          value={age}
          type="number"
          onChange={e => {
            setAge(+e.target.value);
          }}
        />
      </div>
    );
  }

  const [license, setLicense] = useState('A123456');

  return (
    <div>
      Name:{' '}
      <input
        value={name}
        onChange={e => {
          setName(e.target.value);
        }}
      />
      <br />
      Age:{' '}
      <input
        value={age}
        type="number"
        onChange={e => {
          setAge(+e.target.value);
        }}
      />
      <br />
      Driver License:{' '}
      <input
        value={license}
        onChange={e => {
          setLicense(e.target.value);
        }}
      />
    </div>
  );
}

ReactDOM.render(<App />, document.querySelector('#app'));
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>
<div id="app"></div>
3个回答

问题在于,在第一次渲染中,调用了 3 个 useState 钩子 - nameagelicense ,但是在 age 更改为低于 16 的值后, licenseuseState 不再被调用,导致只调用前 2 个钩子。正如 React 文档所述

Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls.

调用钩子的顺序很重要,如果我们编写的代码导致钩子不被调用,React 将无法将钩子调用与其值匹配。

解决方案是将 license 钩子移到函数的顶部,这样无论是否需要它,都会被调用。

const {useState} = React;

function App() {
  const [name, setName] = useState('Mary');
  const [age, setAge] = useState(16);
  const [license, setLicense] = useState('A123456');

  return (
    <div>
      Name:{' '}
      <input
        value={name}
        onChange={e => {
          setName(e.target.value);
        }}
      />
      <br />
      Age:{' '}
      <input
        value={age}
        type="number"
        onChange={e => {
          setAge(+e.target.value);
        }}
      />
      {age >= 16 && <span>
        <br />
        Driver License:{' '}
        <input
          value={license}
          onChange={e => {
            setLicense(e.target.value);
          }}
        /></span>
       }
    </div>
  );
}

ReactDOM.render(<App />, document.querySelector('#app'));
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>
<div id="app"></div>
Yangshun Tay
2018-11-25

我已通过更改 React Hooks 的顺序解决了此问题。 useEffect 在早期 return 之后出现。

因此,我已将代码从

function (){    
    const [loading ,setLoading] = React.useState(false);
    
    if (loading) {
        return "loading ....."
    }
    
    useEffect(() => {
      if (condition) {
        doSomething();
      }
    }, []);
    
    return <div>component</div>
}

更改为

function (){    
    const [loading ,setLoading] = React.useState(false);
   
    useEffect(() => {
      if (condition) {
        doSomething();
      }
    }, []);
     
    if (loading){
      return "loading ....."
    }

    return <div>component</div>
}

因此,当 loading 变为 true 时, useEffect 将不会运行,并且会引发错误。但如果我在 useEffect 之后声明加载 JSX 元素,那么它就会起作用。

Jayesh Ladumor
2020-06-02

确保您没有有条件地运行 useEffect

例如,如果您有如下代码:

if(condition) {
  useEffect(()=>{
    doSomething();
  }, []);
}

然后将其更改为

useEffect(()=>{
  if(condition) {
    doSomething();
  }
}, []);

则不会发生错误。

Jeff Tian
2020-03-03