最近开发了一个React拖拽排序组件库,计划通过npm link到本地测试时,遇到了报错:Warning: Invalid hook call. Hooks can only be called inside of the body of a function component.经过多轮排查,最终发现到React多实例冲突与pnpm对peerDependencies的隐式安装是核心原因,以下是完整解决流程,供同类问题参考
一、问题初判:React版本兼容性?
测试项目使用Next 15(App Router)报错,初步怀疑版本冲突:
- Next 15 App Router默认依赖React 19,但组件库用了React 18.3.1
- 尝试放宽版本限制:将组件库
package.json中react的版本范围改为"^18.3.1 || ^19.0.0",但测试仍报错 - 结论:版本兼容性非主因(组件库并没有使用与react18强相关的特性,后来使用了一个react18的项目也不行)
二、排查方向:组件库依赖配置问题
查阅React官方文档(Invalid Hook Call警告)和关键Issue(github.com/facebook/re… ,提示可能是React多实例或Hooks未在函数组件内调用导致。因为组件代码没有问题所以排除后者,重点排查多实例:
1. 验证多实例的关键方法
-
现象辅助判断:同时出现
Invalid hook call和Cannot read properties of null (reading 'useState'),是典型多实例特征(不同React实例的Hooks上下文不共享) -
打包产物检查:
- 构建后搜索组件库代码,确认仅在入口有
import React from 'react',其他位置无重复引入或者明显的function useState(){}定义等 - 使用
rollup-plugin-visualizer分析打包依赖,确认React未被打包进组件库
- 构建后搜索组件库代码,确认仅在入口有
2. 修复尝试:配置peerDependencies与外部化
- peerDependencies声明:在组件库
package.json中,将react和react-dom标记为peerDependencies(告知用户需自行安装) - Rollup外部化配置:在
vite.config.ts中,通过build.rollupOptions.external将React相关模块排除在打包外:["react", "react-dom", "react/jsx-runtime"]关键:包括react/jsx-runtime - 结果:仍报错
三、关键突破:React路径指向异常
尝试暴力验证:将组件库打包后的import React from 'react'改为测试项目中React的绝对路径(如import React from '/path/to/test-project/node_modules/react'),测试项目运行正常
- 结论:实锤组件库与测试项目引用了不同路径的React实例
四、终极原因:pnpm对peerDependencies的隐式安装
进一步排查依赖管理工具pnpm的特性:
- pnpm默认行为:pnpm 10默认开启
autoInstallPeers(官方文档)(pnpm9也是),会自动安装peerDependencies到当前项目的node_modules中(这篇文章stackoverflow.com/questions/7… 有误导性) - 问题触发场景:组件库依赖了
motion,而motion的peerDependencies中声明了react,pnpm因autoInstallPeers=true,会隐式为组件库安装一个独立的React实例 - 验证方法:执行
pnpm why react(显示哪个包依赖了react导致的下载),输出显示React由motion的peer依赖触发安装
五、最终解决方案
通过配置pnpm禁用自动安装peer依赖,确保组件库与测试项目共享同一React实例:
- 在组件库根目录创建
.npmrc文件,添加:auto-install-peers = false - 删除lock文件,重新安装依赖(
pnpm i),可以看到pnpm-lock.yaml中开头有一行autoInstallPeers: false - 重新build组件库
总结与避坑指南
-
核心原则:本地测试组件库时,确保组件库与测试项目共享同一React实例(路径、版本完全一致)
-
关键配置:
- 组件库必须声明
react为peerDependencies,避免打包时包含React - Rollup/Vite需外部化
react、react-dom、react/jsx-runtime(避免打包) - pnpm用户需检查
auto-install-peers配置(默认开启,可能导致隐式安装独立实例)
- 组件库必须声明
tips:npm link和pnpm link的区别
- npm link:先
npm link将组件库注册到全局,再在测试项目npm link 包名引用全局链接,即通过全局node_modules建立软链接 - pnpm link:直接将组件库路径硬链接到测试项目的
node_modules(需显式指定路径pnpm link 组件库路径),无需经过全局node_modules
希望这篇记录能帮到遇到类似问题的开发者