React Hooks开发的npm包引入报Invalid hook call错误解决方案
问题复现表现
自研UI组件库打包发布后,在业务测试项目中引入时:
- 未使用任何Hook的纯渲染函数组件可正常展示
- 调用了
useEffect等内置Hook的组件抛出错误:Invalid hook call. Hooks can only be called inside of the body of a function component,无法正常渲染 - 尝试将
react、react-dom从dependencies迁移到peerDependencies后,问题没有解决
核心原因
报错的本质是组件库运行时和业务项目加载了两个独立的React实例,违反了React Hooks的单实例约束。
仅把React声明为peerDependencies只是告知包管理器“当前库需要宿主环境提供对应版本的React”,但webpack打包时默认会把所有import/require的依赖打进产物,如果没有配置外置规则,React源码依然会被打包到组件库的产物里。运行时组件库用自己内置的React跑Hook逻辑,业务项目用自身node_modules下的React做渲染调度,两个实例的Hooks状态池不互通,直接触发Hook调用规则校验。
不涉及Hook的普通组件不会走到React内部的Hooks状态校验逻辑,所以可以正常渲染,这也是两类组件表现不一致的原因。
分步修复操作
1. 调整package.json依赖声明
把react、react-dom从dependencies中移除,放到peerDependencies声明宿主依赖要求,同时在devDependencies中添加本地开发需要的React版本,避免开发环境找不到依赖:
{ "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" }, "devDependencies": { "react": "^18.2.0", "react-dom": "^18.2.0" } }
版本号根据你实际开发使用的版本替换即可,不要把react/react-dom放到dependencies中,否则用户安装时可能重复拉取多份React副本
2. 修改webpack打包配置,外置React相关依赖
在webpack配置中添加externals字段,明确告知打包工具不要将react、react-dom打入组件库产物,运行时直接从宿主业务项目中获取对应依赖:
// webpack.config.js const path = require('path'); module.exports = { mode: 'production', entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'index.js', libraryTarget: 'commonjs2' }, // 新增externals配置 externals: { react: { commonjs: 'react', commonjs2: 'react', root: 'React' }, 'react-dom': { commonjs: 'react-dom', commonjs2: 'react-dom', root: 'ReactDOM' } }, module: { rules: [ // 原有babel-loader等规则保持不变 { test: /\.jsx?$/, exclude: /node_modules/, use: 'babel-loader' } ] } };
3. 本地联调特殊处理(npm link场景)
如果是用npm link做本地联调,很容易因为软链路径层级问题导致React实例重复,可通过两种方式解决:
- 组件库执行
npm link后,再执行npm link [测试项目绝对路径]/node_modules/react,强制组件库本地调试时复用测试项目的React实例 - 在测试项目的webpack配置中添加别名,强制所有模块引用的React都指向项目本地的React路径:
// 测试项目webpack.config.js const path = require('path'); module.exports = { resolve: { alias: { react: path.resolve(__dirname, 'node_modules/react'), 'react-dom': path.resolve(__dirname, 'node_modules/react-dom') } } };
4. 最终校验
重新执行组件库打包命令,对比打包产物体积,会发现比配置externals之前小很多(React源码不再被内置)。此时再在测试项目中引入带Hook的组件,即可正常渲染,不会再抛Hook调用错误。
补充排查:如果完成以上操作后仍有报错,在项目根目录执行
npm ls react查看依赖树,检查是否存在多个版本的React被安装,确保整个项目运行时只会加载一份符合版本要求的React实例。
内容的提问来源于stack exchange,提问作者Mormen

