63

给定以下组件,当我按下年龄选择器并将值更改为 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/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>

4

5 回答 5

84

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

不要在循环、条件或嵌套函数中调用 Hook。相反,请始终在 React 函数的顶层使用 Hooks。通过遵循此规则,您可以确保每次渲染组件时都以相同的顺序调用 Hook。这就是允许 React 在多个useStateuseEffect调用之间正确保留 Hooks 状态的原因。

调用钩子的顺序很重要,如果我们编写导致钩子不被调用的代码,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/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>

于 2018-11-25T22:51:06.877 回答
23

我已经通过更改 React Hooks 的顺序解决了这个问题。

所以我改变了代码

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>
}

因此,当加载变为真时,useEffect 将不会运行,并且会抛出错误..但是如果我在 useEffect 之后声明加载 JSX 元素,那么它将完美运行。

于 2020-06-02T08:29:11.727 回答
11

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

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

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

然后将其更改为

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

那么错误就不会发生。

于 2020-03-03T11:15:45.233 回答
5

所以我也遇到了这个错误,但它与钩子被包裹在一个条件中无关。相反,这是由于我的键值错误造成的。

我有一个组件通过拖放将项目从一个列表移动到另一个列表。但是,其中一些项目被锁定了,所以我只是删除了这些元素中对我的钩子的引用。

问题是我使用索引作为键,因此当我在顶部拖动一个新元素时,它得到与锁定元素相同的键,ref 的值被更改,React 抱怨上述错误。

所以我对遇到这个问题的人的回答是——检查你是否有真正唯一的钥匙!

于 2020-08-05T15:11:05.913 回答
0

今天我刚刚遇到这个错误,我通过改变钩子的位置来修复它:useEffect()从函数的中间到标题。并确保不要在循环中使用钩子。

如果某些值在重新渲染之间没有改变,你可以告诉 React 跳过应用效果。为此,请将数组作为可选的第二个参数传递给 useEffect:

      useEffect( () => {
        dispatch({type: 'GET_PAGE', slug: Param});
        setPagedata( data );
      });


      useEffect( () => {
        dispatch({type: 'GET_PAGE', slug: Param});
      }, [Param]);

      useEffect( () => {
        setPagedata( data );
      }, [data]);

它有效!

于 2021-09-05T03:51:30.137 回答