大佬,第三方组件的Hooks为啥报错了?

  发布时间:2025-11-05 08:49:02   作者:玩站小弟   我要评论
最近工作中遇到个有意思的问题,记录下从问题发现到解决的过程。这个问题涉及知识点包括:hooks源码逻辑 package.json配置 事发某个需求需要引入一个第三方组件库。 。

 最近工作中遇到个有意思的大佬问题,记录下从问题发现到解决的第方的过程。

这个问题涉及知识点包括:

hooks源码逻辑 package.json配置

事发

某个需求需要引入一个第三方组件库。组件

当引入组件库中的为啥函数组件A后,React运行时报错:

"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...

从React文档了解到,这是大佬由于「错误使用Hooks造成的」。

官网给出的第方的可能的错误原因有3种:

1.React和ReactDOM版本不匹配

需要v16.8以上版本的ReactDOM才支持Hooks。

我们项目使用的组件是v17.0.2,不属于这个原因。为啥

2.打破了Hooks的报错规则

Hooks只能在函数组件或自定义Hooks顶层调用。

翻看A组件源码,大佬报错的第方的是一个顶层调用的useRef:

function A() {   // ...   var xxxRef = useRef(null);   // ... } 

不属于这个原因。

3.重复的组件React

载录自React文档:

为了使 Hook 正常工作,你应用代码中的为啥 react 依赖以及 react-dom 的 package 内部使用的源码下载 react 依赖,必须解析为同一个模块。报错 如果这些 react 依赖解析为两个不同的导出对象,你就会看到本警告。这可能发生在你意外地引入了两个 react 的 package 副本。

读起来好绕,看起来这条的嫌疑最大。

定位问题

在报错的useRef中打上断点,发现其来自于:

http://localhost:8081/Users/项目目录/node_modules/组件库/node_modules/react/cjs/react.development.js

在项目里其他调用Hooks但是未报错的地方打上断点,发现资源来自于:

http://localhost:8081/Users/项目目录/node_modules/react/cjs/react.development.js

报错的useRef和项目其他Hooks引用了不同的react.development.js。

翻看「组件库」的package.json,发现他将react与react-dom作为dependencies安装:

"dependencies": {   "react": "^16.13.1",   "@babel/runtime-corejs3": "^7.11.2",   "react-dom": "^16.13.1" }, 

这样会在「组件库」目录的node_modules下创建这两个依赖。

作为一个「组件库」,这么做显然是不合适的。

临时解决

最好的做法是将这两个依赖作为peerDependencies,即将其作为外部依赖。

这样,当我们引入「组件库」时,「组件库」会使用我们项目中的云服务器提供商react与react-dom,而不是自己安装一份。

但是我没有这个「组件库」的权限,只能在自己项目中做文章。

在package.json文档中提供了一个配置项:resolutions,可以临时解决这个问题。

resolutions允许你复写一个在项目node_modules中被嵌套引用的包的版本。

在我们项目的package.json中作出如下修改:

// 项目package.json {   // ...   "resolutions": {     "react": "17.0.2",     "react-dom": "17.0.2"   },   // ... } 

这样,项目中用到的这两个依赖都会使用resolutions中指定的版本。

不管是「组件库」还是我们的项目代码中的react与react-dom,都会指向同一个文件。

现在问题是临时解决了,但是造成问题的原因是什么?

让我们深入Hooks源码内部来寻找答案。

深入源码

首先让我们思考2个问题:

当我们在一个Hooks内部调用其他Hooks时会报开篇提到的错误。

比如如下代码就会报错:

function App() {   useEffect(() => {     const a = useRef();   }, [])   // ... } 

Hooks只是函数,站群服务器他如何感知到自己在另一个Hooks内部执行?

就如上例子,useRef如何感知到自己在useEffect的回调函数中执行?

再看另一个问题,我们知道classComponent有componentDidMount与componentDidUpdate两个生命周期函数区分mount时与update时。

那么Hooks作为函数,怎么区分当前是mount时还是update时?

显然,Hooks源码内部存在一种机制,能够感知当前执行的上下文环境。

渐入佳境

在浏览器环境,我们会引用react与reactDOM两个包。

其中,在react包的代码中存在一个变量ReactCurrentDispatcher。

他的current参数指向当前正在使用的Hooks上下文:

var ReactCurrentDispatcher = {   /**    * @internal    * @type {ReactComponent}    */   current: null }; 

同时,在reactDOM中,在程序运行过程中,ReactCurrentDispatcher.current会根据当前上下文环境指向不同引用。

比如:

var HooksDispatcherOnMountInDEV = {   useState: function() { // ... },   useEffect: function() { // ... },   useRef: function() { // ... },   // ... } var HooksDispatcherOnUpdateInDEV = {   useState: function() { // ... },   useEffect: function() { // ... },   useRef: function() { // ... },   // ... } // ... 

当处在DEV环境mount时,ReactCurrentDispatcher.current会指向HooksDispatcherOnMountInDEV。

当处在DEV环境update时,ReactCurrentDispatcher.current会指向HooksDispatcherOnUpdateInDEV。

再来看useRef的定义:

function useRef(initialValue) {   var dispatcher = resolveDispatcher();   return dispatcher.useRef(initialValue); } 

内部调用的是dispatcher.useRef。

dispatcher即ReactCurrentDispatcher.current。

function resolveDispatcher() {   var dispatcher = ReactCurrentDispatcher.current;   if (!(dispatcher !== null)) {     {       throw Error( "Invalid hook call. ..." );     }   }   return dispatcher; }  可以看到,开篇的错误正是由于dispatcher为null时抛出

这就是Hooks能区分mount与update的原因。

同理,DEV环境,当一个Hooks在执行时,ReactCurrentDispatcher.current会指向引用 —— InvalidNestedHooksDispatcherOnUpdateInDEV。

在这种情况下再调用的Hooks,比如如下useRef:

var InvalidNestedHooksDispatcherOnUpdateInDEV = {   // ...   useRef: function (initialValue) {     currentHookNameInDev = useRef;     warnInvalidHookAccess();     updateHookTypesDev();     return updateRef();   },   // ... } 

内部都会执行warnInvalidHookAccess报错,提示自己在别的Hooks内执行了。

真相大白

到这里我们终于知道开篇提到的问题发生的本质原因:

由于「组件库」使用dependencies而不是peerDependencies,导致「组件库」中引用的react与reactDOM是「组件库」目录node_modules下的文件。 项目中使用的react与reactDOM是项目目录node_modules下的文件。 「组件库」中react与项目目录中react在运行时分别初始化ReactCurrentDispatcher 这两个ReactCurrentDispatcher分别依赖对应目录的reactDOM 我们在项目中执行项目目录下reactDOM的ReactDOM.render方法,他会随着程序运行改变项目目录中react包下的ReactCurrentDispatcher.current的指向 「组件库」中的ReactCurrentDispatcher.current始终是null 当调用「组件库」中的Hooks时,由于ReactCurrentDispatcher.current始终是null导致报错

总结

通过分析这个问题,加深了对package.json以及Hooks源码的理解。

不知道Hooks感知上下文的实现思路对你有没有启发呢?

  • Tag:

相关文章

  • 电脑硬盘安装教程(详细步骤帮助你轻松完成硬盘的安装和连接)

    摘要:电脑硬盘的安装是一项关键的任务,不仅可以增加电脑的存储空间,还能提高其运行速度。但对于没有经验的用户来说,硬盘安装可能会成为一项复杂的任务。本教程将一步步指导您如何安装电脑硬盘,确...
    2025-11-05
  • 震惊!用 Redis+AI 模型实现秒级实时风控,这波操作太秀了

    兄弟们,有没有遇到过这种情况:凌晨三点在某东抢购显卡,刚提交订单就提示"系统繁忙",转头发现黄牛已经在海鲜市场挂出同款;扫码支付时突然弹出风险提示,非要验证人脸识别;更绝的是某银行APP,刚输完密码就
    2025-11-05
  • 物联网的来龙去脉

    1926年,尼古拉·特斯拉NikolaTesla)梦想着一个更好的互联世界:地球将通过袖珍型设备变成一个巨大的大脑。快进到 2022 年,随着越来越多的设备连接到物联网(IoT),特斯拉的梦想比以往任
    2025-11-05
  • 常见SQL慢查询问题及解决方法

    前言在数据库管理中,SQL慢查询是经常遇到的问题,严重影响系统的性能和用户体验。本文将详细介绍几种常见的 SQL 慢查询问题,并结合具体例子给出相应的解决方法。案例在索引列上使用函数即使创建了索引,某
    2025-11-05
  • 老桃毛装机教程(教你一步步自己动手,从零开始组装属于自己的电脑)

    摘要:在如今高科技发达的时代,电脑已经成为我们生活中不可或缺的一部分。如果你对电脑有着浓厚的兴趣,想要打造一个独一无二的个性化电脑,那么老桃毛装机教程将会是你的不二选择。通过本教程,你将...
    2025-11-05
  • 老旧系统安全防护:现代化改造策略

    在企业加速数字化转型的进程中,许多机构仍受制于老旧系统Legacy Systems)——这些支撑核心业务运营的技术虽显陈旧却至关重要。尽管这些系统通常能稳定执行既定任务,但其过时的架构使企业面临安全风
    2025-11-05

最新评论