React + Antd + Rollup 组件库“错误:无效的钩子调用。钩子只能在函数组件的主体内部调用”

Posted

技术标签:

【中文标题】React + Antd + Rollup 组件库“错误:无效的钩子调用。钩子只能在函数组件的主体内部调用”【英文标题】:React + Antd + Rollup Component Library "Error: Invalid hook call. Hooks can only be called inside of the body of a function component" 【发布时间】:2021-03-23 07:45:12 【问题描述】:

我目前正在构建一个 UI 库来简化跨多个应用程序的维护。这些目前使用 Ant Design。

一切似乎都很好...我在 package.jsonrollup.config.js 中添加了我的对等依赖项(通过外部),我能够让 Rollup 生成一个成功导出的 es 和 cjs 二进制文件只是 我的代码。

但是,当我将其中任何一个导入我的主机应用程序(Electron 和/或 React,已经使用 antd 没有问题)时,我收到以下错误:

Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
    at resolveDispatcher (react.development.js:1476)
    at Object.useContext (react.development.js:1484)
    at Button (button.js:129)
    at renderWithHooks (react-dom.development.js:14985)
    at updateForwardRef (react-dom.development.js:17044)
    at beginWork (react-dom.development.js:19098)
    at htmlUnknownElement.callCallback (react-dom.development.js:3945)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:3994)
    at invokeGuardedCallback (react-dom.development.js:4056)
    at beginWork$1 (react-dom.development.js:23964)
resolveDispatcher @ react.development.js:1476
useContext @ react.development.js:1484
Button @ button.js:129
renderWithHooks @ react-dom.development.js:14985
updateForwardRef @ react-dom.development.js:17044
beginWork @ react-dom.development.js:19098
callCallback @ react-dom.development.js:3945
invokeGuardedCallbackDev @ react-dom.development.js:3994
invokeGuardedCallback @ react-dom.development.js:4056
beginWork$1 @ react-dom.development.js:23964
performUnitOfWork @ react-dom.development.js:22779
workLoopSync @ react-dom.development.js:22707
renderRootSync @ react-dom.development.js:22670
performSyncWorkOnRoot @ react-dom.development.js:22293
scheduleUpdateOnFiber @ react-dom.development.js:21881
updateContainer @ react-dom.development.js:25482
(anonymous) @ react-dom.development.js:26021
unbatchedUpdates @ react-dom.development.js:22431
legacyRenderSubtreeIntoContainer @ react-dom.development.js:26020
render @ react-dom.development.js:26103
(anonymous) @ renderer.tsx:129
./src-template/renderer.tsx @ renderer.tsx:150
__webpack_require__ @ bootstrap:789
fn @ bootstrap:100
0 @ renderer.tsx:150
__webpack_require__ @ bootstrap:789
(anonymous) @ bootstrap:856
(anonymous) @ bootstrap:856
react-dom.development.js:20085 The above error occurred in the <Button> component:

    at Button (http://localhost:3000/main_window/index.js:48908:30)
    at ../../ui-library/dist/index.cjs.js.exports.ComponentA (http://localhost:3000/main_window/index.js:101188:13)
    at div
    at App (http://localhost:3000/main_window/index.js:204727:30)

我无法理解如何继续...我试图调整我的汇总配置(如下)并将我的所有代码剥离为一个测试器组件(antd 按钮),但我仍然遇到错误。

当我console.log() 导入对象时,我可以看到 es 和 cjs 二进制文件都公开了测试器组件,但存在错误。

我在这里错过了什么?

对等依赖

反应 反应 DOM 蚂蚁 @ant-design/icons

Rollup.config.js

import  DEFAULT_EXTENSIONS  from '@babel/core'
import babel from '@rollup/plugin-babel'
import typescript from 'rollup-plugin-typescript2'
import commonjs from '@rollup/plugin-commonjs'
import external from 'rollup-plugin-peer-deps-external'
import postcss from 'rollup-plugin-postcss'
import resolve from '@rollup/plugin-node-resolve'
import url from '@rollup/plugin-url'
import svgr from '@svgr/rollup'
import  terser  from 'rollup-plugin-terser'

import pkg from './package.json'


const isDevelopment = process.env.NODE_ENV === 'development' ? true : false;

console.log('EXPECTED EXTERNALS', [
      ...Object.keys(pkg.dependencies || ),
      ...Object.keys(pkg.peerDependencies || )
])
export default 
  input: 'src/index.jsx',
  output: [
    
      file: `dist/index.es.js`,
      format: 'esm',
      exports: 'named',
      sourcemap: isDevelopment,
    ,
    
      file: `dist/index.cjs.js`,
      format: 'cjs',
      exports: 'named',
      sourcemap: isDevelopment,
    
  ],
  context: 'this',
  external: [
        ...Object.keys(pkg.dependencies || ),
        ...Object.keys(pkg.peerDependencies || )
  ],
  plugins: [
    external(),
    typescript(
      rollupCommonJSResolveHack: true,
      clean: true,
      tsconfig: './tsconfig.json',
    ),
    babel(
      presets: [
        'react-app',
      ],
      extensions: [
        ...DEFAULT_EXTENSIONS,
        '.ts',
        '.tsx',
      ],
      plugins: [
        ["import",  "libraryName": "antd", "libraryDirectory": "es", "style": "css" ],
        "@babel/plugin-proposal-object-rest-spread",
        "@babel/plugin-proposal-optional-chaining",
        "@babel/plugin-syntax-dynamic-import",
        "@babel/plugin-proposal-class-properties",
        "transform-react-remove-prop-types"
      ],
      babelHelpers: 'runtime',
    ),
    postcss(
        extensions: ['.css', '.scss', '.less'],
        use: ['sass', ['less', 
          lessOptions: 
             javascriptEnabled: true
          
        ]],
    ),
    svgr(),
    url(),
    resolve(),
    commonjs(),
    terser( mangle: true ),
  ],


Package.json(组件库)


  "name": "ui-library",
  "version": "0.0.1",
  "description": "UI library components",
  "main": "index.js",
  "scripts": 
    "build": "rm -rf dist && mkdir dist && NODE_ENV=production BABEL_ENV=production rollup -c"
  ,
  "peerDependencies": 
    "@ant-design/icons": "^4.3.0",
    "antd": "^4.9.2",
    "react": "^17.0.1",
    "react-dom": "^17.0.1"
  ,
  "devDependencies": 
    "@babel/core": "^7.12.10",
    "@babel/plugin-proposal-class-properties": "^7.12.1",
    "@babel/plugin-proposal-object-rest-spread": "^7.12.1",
    "@babel/plugin-proposal-optional-chaining": "^7.12.7",
    "@babel/plugin-syntax-dynamic-import": "^7.8.3",
    "@babel/plugin-transform-react-jsx": "^7.12.10",
    "@babel/preset-env": "^7.12.10",
    "@babel/preset-react": "^7.12.10",
    "@babel/preset-typescript": "^7.12.7",
    "@rollup/plugin-babel": "^5.2.2",
    "@rollup/plugin-commonjs": "^17.0.0",
    "@rollup/plugin-node-resolve": "^11.0.0",
    "@rollup/plugin-typescript": "^8.0.0",
    "@rollup/plugin-url": "^6.0.0",
    "@svgr/rollup": "^5.5.0",
    "@types/node": "^14.14.11",
    "@types/react": "^17.0.0",
    "@types/react-dom": "^17.0.0",
    "@typescript-eslint/eslint-plugin": "^4.9.1",
    "@typescript-eslint/parser": "^4.9.1",
    "babel-loader": "^8.2.2",
    "babel-plugin-import": "^1.13.3",
    "babel-plugin-transform-react-remove-prop-types": "^0.4.24",
    "babel-preset-react-app": "^10.0.0",
    "css-loader": "^4.2.1",
    "eslint": "^7.15.0",
    "eslint-config-airbnb-typescript": "^12.0.0",
    "eslint-plugin-react": "^7.21.5",
    "less-loader": "^7.1.0",
    "mini-css-extract-plugin": "^1.3.2",
    "react-is": "^17.0.1",
    "rollup": "^2.34.2",
    "rollup-plugin-peer-deps-external": "^2.2.4",
    "rollup-plugin-postcss": "^4.0.0",
    "rollup-plugin-terser": "^7.0.2",
    "rollup-plugin-typescript2": "^0.29.0",
    "sass-loader": "^10.1.0",
    "style-loader": "^2.0.0",
    "typescript": "^4.1.2",
    "url-loader": "^4.1.1"
  


组件库测试器组件

import React from 'react';

import  Button, Radio  from 'antd';
import  DownloadOutlined  from '@ant-design/icons';
import  SizeType  from 'antd/lib/config-provider/SizeContext';


export interface ButtonProps 
  /**
   * What background color to use
   */
  backgroundColor?: string;
  /**
   * Button contents
   */
  label: string;

  /**
   * Size (small | large)
   */
  size: SizeType;
  /**
   * Optional click handler
   */
  onClick?: () => void;



// export const ComponentA = (props: ButtonProps) => (<button type="button" onClick=props.onClick style= backgroundColor: props.backgroundColor> props.label </button>);

export const ComponentA = (props: ButtonProps) => (
  <Button
    type="primary"
    shape="round"
    icon=<DownloadOutlined />
    size=props.size || 'middle'
    onClick=props.onClick || null
  >
    props.label || ''
  </Button>
)

更新:添加 rollup-plugin-visualizer 输出

【问题讨论】:

另外,如果你想检查捆绑包的内容,尝试安装 rollup-plugin-visualizer,这样你就可以确定 react 它不是捆绑包的一部分。 嗨@lissettdm,感谢您的建议。可悲的是,显式设置 externals 数组没有效果,我可以看到 React 或 ReactDOM 不在 rollup-plugin-visualizer 的可视化中(请参阅原始帖子以获取更新) 你在使用 npm 链接吗? @lissettdm 正要再试一次 【参考方案1】:

如果您在主项目中链接库的本地版本以加快开发速度时发生此问题。它可能与“React 的重复版本”有关。

https://reactjs.org/warnings/invalid-hook-call-warning.html

当您使用 npm link 或等效项时,也会出现此问题。在这种情况下,你的打包器可能会“看到”两个 React——一个在应用程序文件夹中,一个在你的库文件夹中。假设 myapp 和 mylib 是同级文件夹,一种可能的解决方法是从 mylib 运行 npm link ../myapp/node_modules/react。这应该使库使用应用程序的 React 副本。

简而言之:

在 /your-app/node_modules/react 中运行 npm link。这应该是 React 的全局链接。 在 /your-ui-library 中运行 npm link react。这应该使库使用应用程序的 React 副本。

【讨论】:

嗨...只是在我的计算机上的my-app/node_modules/react 中确认npm link,然后在ui-library 存储库中确认npm link react?然后再次构建并运行 my-app? 好的,如上所述,我做了 npm 链接,它现在已经修复了错误。我将尝试发布到 GitLab 并查看该库是否仍然有效,或者我是否可以以某种方式重现此问题。我想我需要不断重复这一步? 这种情况发生在我身上好几次了,我从来没有尝试过这个选项,很高兴知道。 这是一个狭隘的修复。每次我 npm install 时都要记住这样做会令人沮丧。想看看当我将它发布到我的注册表并将包作为依赖项安装时会发生什么,想知道这一步是否专门用于本地导入 是的,我在回答中提到,错误可能是本地开发引起的。我认为您在发布库时不会遇到这个问题。理论上,无论如何都应该避免这一步,因为您的 ui 库组件应该独立开发,而不依赖于您的主项目。您可以使用 Storybook 之类的工具来帮助您解决此问题,这也是记录 ui-lib 组件用例的好方法。【参考方案2】:

我有同样的问题,使用 rush monorepo,配置 ui-package 并使用 nextjs 构建应用程序。

解决方案是在package.json上添加以下代码

"resolutions": 
  "react": "17.0.1", // verificate your react version
  "react-dom": "17.0.1"

在此处查看更多信息:https://github.com/vercel/next.js/issues/9022#issuecomment-728688452

【讨论】:

以上是关于React + Antd + Rollup 组件库“错误:无效的钩子调用。钩子只能在函数组件的主体内部调用”的主要内容,如果未能解决你的问题,请参考以下文章

为 React 组件库使用 Rollup

Rollup.js 没有捆绑我的 React 组件库字体

React UI组件库——如何快速实现antd的按需引入和自定义主题

antd树选择组件筛选功能(Tree&TreeSelect)

基于react的UI组件库(antd)使用入门

rollup开发自己的组件库(5)