一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - 編程技術 - 大佬,第三方組件的Hooks為啥報錯了?

大佬,第三方組件的Hooks為啥報錯了?

2021-04-09 23:12魔術師卡頌卡頌 編程技術

有朋友在工作中遇到了一個問題,第三方組件的Hooks為啥報錯了?本篇就詳細介紹一下解決的過程。

大佬,第三方組件的Hooks為啥報錯了?

最近工作中遇到個有意思的問題,記錄下從問題發現到解決的過程。

這個問題涉及知識點包括:

  • 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:

  1. function A() { 
  2.   // ... 
  3.   var xxxRef = useRef(null); 
  4.   // ... 

不屬于這個原因。

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安裝:

  1. "dependencies": { 
  2.   "react""^16.13.1"
  3.   "@babel/runtime-corejs3""^7.11.2"
  4.   "react-dom""^16.13.1" 
  5. }, 

這樣會在「組件庫」目錄的node_modules下創建這兩個依賴。

作為一個「組件庫」,這么做顯然是不合適的。

臨時解決

 

最好的做法是將這兩個依賴作為peerDependencies,即將其作為外部依賴。

這樣,當我們引入「組件庫」時,「組件庫」會使用我們項目中的react與react-dom,而不是自己安裝一份。

但是我沒有這個「組件庫」的權限,只能在自己項目中做文章。

在package.json文檔中提供了一個配置項:resolutions,可以臨時解決這個問題。

resolutions允許你復寫一個在項目node_modules中被嵌套引用的包的版本。

在我們項目的package.json中作出如下修改:

  1. // 項目package.json 
  2.   // ... 
  3.   "resolutions": { 
  4.     "react""17.0.2"
  5.     "react-dom""17.0.2" 
  6.   }, 
  7.   // ... 

這樣,項目中用到的這兩個依賴都會使用resolutions中指定的版本。

不管是「組件庫」還是我們的項目代碼中的react與react-dom,都會指向同一個文件。

現在問題是臨時解決了,但是造成問題的原因是什么?

讓我們深入Hooks源碼內部來尋找答案。

深入源碼

 

首先讓我們思考2個問題:

當我們在一個Hooks內部調用其他Hooks時會報開篇提到的錯誤。

比如如下代碼就會報錯:

  1. function App() { 
  2.  
  3.   useEffect(() => { 
  4.     const a = useRef(); 
  5.   }, []) 
  6.  
  7.   // ... 

Hooks只是函數,他如何感知到自己在另一個Hooks內部執行?

就如上例子,useRef如何感知到自己在useEffect的回調函數中執行?

再看另一個問題,我們知道classComponent有componentDidMount與componentDidUpdate兩個生命周期函數區分mount時與update時。

那么Hooks作為函數,怎么區分當前是mount時還是update時?

顯然,Hooks源碼內部存在一種機制,能夠感知當前執行的上下文環境。

漸入佳境

 

在瀏覽器環境,我們會引用react與reactDOM兩個包。

其中,在react包的代碼中存在一個變量ReactCurrentDispatcher。

他的current參數指向當前正在使用的Hooks上下文:

  1. var ReactCurrentDispatcher = { 
  2.   /** 
  3.    * @internal 
  4.    * @type {ReactComponent} 
  5.    */ 
  6.   currentnull 
  7. }; 

同時,在reactDOM中,在程序運行過程中,ReactCurrentDispatcher.current會根據當前上下文環境指向不同引用。

比如:

  1. var HooksDispatcherOnMountInDEV = { 
  2.   useState: function() { // ... }, 
  3.   useEffect: function() { // ... }, 
  4.   useRef: function() { // ... }, 
  5.   // ... 
  6. var HooksDispatcherOnUpdateInDEV = { 
  7.   useState: function() { // ... }, 
  8.   useEffect: function() { // ... }, 
  9.   useRef: function() { // ... }, 
  10.   // ... 
  11. // ... 

當處在DEV環境mount時,ReactCurrentDispatcher.current會指向HooksDispatcherOnMountInDEV。

當處在DEV環境update時,ReactCurrentDispatcher.current會指向HooksDispatcherOnUpdateInDEV。

再來看useRef的定義:

  1. function useRef(initialValue) { 
  2.   var dispatcher = resolveDispatcher(); 
  3.   return dispatcher.useRef(initialValue); 

內部調用的是dispatcher.useRef。

dispatcher即ReactCurrentDispatcher.current。

  1. function resolveDispatcher() { 
  2.   var dispatcher = ReactCurrentDispatcher.current
  3.  
  4.   if (!(dispatcher !== null)) { 
  5.     { 
  6.       throw Error( "Invalid hook call. ..." ); 
  7.     } 
  8.   } 
  9.  
  10.   return dispatcher; 
  • 可以看到,開篇的錯誤正是由于dispatcher為null時拋出

這就是Hooks能區分mount與update的原因。

同理,DEV環境,當一個Hooks在執行時,ReactCurrentDispatcher.current會指向引用 —— InvalidNestedHooksDispatcherOnUpdateInDEV。

在這種情況下再調用的Hooks,比如如下useRef:

  1. var InvalidNestedHooksDispatcherOnUpdateInDEV = { 
  2.   // ... 
  3.   useRef: function (initialValue) { 
  4.     currentHookNameInDev = 'useRef'
  5.     warnInvalidHookAccess(); 
  6.     updateHookTypesDev(); 
  7.     return updateRef(); 
  8.   }, 
  9.   // ... 

內部都會執行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感知上下文的實現思路對你有沒有啟發呢?

原文地址:https://mp.weixin.qq.com/s/AJCtEXKDp-UKM7Lq4scxSQ

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 美女和男人免费网站视频 | 99ri在线精品视频在线播放 | 欧美日韩国产手机在线观看视频 | 蜜色影院 | 99久久伊人精品波多野结衣 | ass亚洲熟妇毛茸茸pics | 国产成人a∨麻豆精品 | 日本在线视频免费观看 | 无码乱人伦一区二区亚洲一 | 国产色司机在线视频免费观看 | 亚洲电影成人 成人影院 | 精品久久国产 | 日本中文字幕永久在线 | chinesemature精品| 99av导航| 久久精品一区 | bestialitysex杂交 bedfriend泰剧全集免费观看 | 亚洲网站大全 | 欧美日韩不卡视频 | 午夜福利体验免费体验区 | 精品免费久久久久久影院 | 香蕉久久久 | 国产精品二区高清在线 | 午夜影院h| 视频久久 | 北条麻妃黑人正在播放 | 91精品91| 日本在线视频网址 | 国产一级黄色网 | 成人一级黄色大片 | 无敌在线视频观看免费 | 欧美男男gaygaysxxx | 性奶老妇 视频 | pron在线观看 | 国产性tv国产精品 | 亚洲国产成人精品无码区5566 | 香蕉成人国产精品免费看网站 | 亚洲国产欧美在线人成aaaa20 | 国产va欧美va在线观看 | 久久精品中文字幕 | 亚洲天堂岛国片 |