为社区建设略尽绵薄之力!参与 2021 社区问卷调查!

警告:非法 Hook 调用

你能到这个页面很可能是因为你看到了以下错误信息:

Hooks can only be called inside the body of a function component.

触发这个警告有三种常见的原因:

  1. 你的 React 和 React DOM 可能版本不匹配
  2. 你可能打破了 Hook 的规则
  3. 你可能在同一个应用中拥有多个 React 副本

让我们依次来看这些情况。

React 和 React DOM 版本不匹配

你可能使用的是尚未支持 Hook 的 react-dom(< 16.8.0)或 react-native(< 0.59)版本。你可以在应用文件夹中执行 npm ls react-domnpm ls react-native 来检当前使用的版本。如果你发现不止一个相关的依赖,可能也会产生问题(在后面的小节会进行说明)。

打破了 Hook 的规则

你只能在当 React 渲染函数组件时调用 Hook:

  • ✅ 在函数组件的顶层调用它们。
  • ✅ 在自定义 Hook 的顶层调用它们。

查看 Hook 的规则以了解更多内容。

function Counter() {
  // ✅ Good:函数组件的顶层  const [count, setCount] = useState(0);  // ...
}

function useWindowWidth() {
  // ✅ Good:自定义 Hook 的顶层  const [width, setWidth] = useState(window.innerWidth);  // ...
}

为避免困惑,在以下情况中调用 Hook 是被支持的:

  • 🔴 不要在 class 组件中调用 Hook。
  • 🔴 不要在 event handlers 中调用。
  • 🔴 不要在 useMemouseReduceruseEffect 的参数函数中调用。

如果你打破了这些规则,那么你可能就会看到本错误。

function Bad1() {
  function handleClick() {
    // 🔴 Bad:在 event handler 内部 (移到外部来修复它)    const theme = useContext(ThemeContext);  }
  // ...
}

function Bad2() {
  const style = useMemo(() => {
    // 🔴 Bad:在 useMemo 内部 (移到外部来修复它)    const theme = useContext(ThemeContext);    return createStyle(theme);
  });
  // ...
}

class Bad3 extends React.Component {
  render() {
    // 🔴 Bad:在 class 组件内部    useEffect(() => {})    // ...
  }
}

你可以使用 eslint-plugin-react-hooks 插件来捕获这些错误。

注意

自定义 Hook 可能会调用其他 Hook(这恰好是他们的用途)。这是可行的,因为自定义 Hook 也应该只在函数组件渲染时被调用。

重复的 React

为了使 Hook 正常工作,你应用代码中的 react 依赖以及 react-dom 的 package 内部使用的 react 依赖,必须解析为同一个模块。

如果这些 react 依赖解析为两个不同的导出对象,你就会看到本警告。这可能发生在你意外地引入了两个 react 的 package 副本

如果你用的是 Node 作为 package 管理工具,你可以执行以下代码来检查你的项目文件夹:

npm ls react

如果你看到多个 React,则需要弄清楚为什么会发生这种情况,并修复 dependency 树。例如,你使用的库可能错误地指定 react 作为 dependency(而不是 peer dependency)。在该库修复之前,Yarn resolutions 是一种可行的解决方案。

你也可以通过添加一些 log 并重启开发服务器来尝试 debug:

// 在 node_modules/react-dom/index.js 中加入这一行
window.React1 = require('react');

// 在你的组件文件中加入这些代码
require('react-dom');
window.React2 = require('react');
console.log(window.React1 === window.React2);

如果打印出 false,那么你可能调用了两个 React,你需要弄清楚为什么会发生这种情况。这个 issue 包含了一些社区中常见的原因。

这个问题也可能发生在你使用 npm link 或等效方案时。在这种情况下,你的打包器可能会“检测”到两个 React —— 一个在应用项目文件夹中,另一个在你的工具库文件夹中。假设 myappmylib 是同级文件夹,一个可能的修复方法是在 mylib 执行 npm link ../myapp/node_modules/react。这将会使得工具库使用应用项目中的 React 副本。

注意

通常,React 支持在单个页面上使用多个独立副本(例如,应用和第三方小部件都使用它)。错误只会发生在,当组件和渲染它的 react-dom 副本两者中的 require('react') 解析不相同时。

其他原因

如果这些方法都不起效,请在这个 issue 中发表评论,我们将尽力提供帮助。尝试创建一个小型的重现示例 —— 重现你会发现问题的做法。

Is this page useful?编辑此页面