不知道大家在用React開發時,有沒有注意到react與react-dom這兩個包中有個很奇葩的屬性__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED:

直譯過來就是「內部神秘屬性,不要亂用!否則你會被炒魷魚」。
為什麼會有個這麼唬人的屬性?今天我們來聊聊。
React項目架構我們在項目中習慣使用如下語句引入Hook:
import{useState}from'react';這是不是意味着所有Hook的具體實現都在react這個包中?實際不是的。
所有Hook的具體實現在ReactFiberHooks.new.js方法中,該方法來自於react-reconciler這個包。
那為什麼我們項目中從來沒有主動引入過這個包呢?因為react-reconciler中被使用的部分,被打包進react-dom中了。
簡單來說,React為了實現跨平台渲染,採用的是「一個主模塊」 + 「一個渲染器」的模式。
其中「主模塊」就是react包,他提供了所有通用方法。
「渲染器」針對宿主環境不同而不同,比如:
瀏覽器環境使用ReactDOM/client渲染器
SSR使用ReactDOM/server渲染器
Native環境使用ReactNative渲染器
渲染器除了「宿主環境相關的代碼」外,還有大量通用邏輯(比如Diff算法)。
所以可以認為,react-dom是由如下多個包中「被使用的部分」打包而成:
shared,一個存放通用方法的包
react-reconciler,提供包括Hooks的實現、Diff算法、優先級調度等更新相關功能
react-dom,提供宿主環境方法,比如「DOM的增/移動/刪/改」
等等其他包
這也是為什麼宿主環境千差萬別,但都能通過執行useState改變狀態,觸發視圖更新。
原因在於 —— 「Hooks的實現」與「宿主環境操作視圖的方法」被打包進了同一個包中。
既然「Hooks的實現」被打包進react-dom(或其他宿主環境對應的包)中,那如何做到最終使用時是從react中導出的呢?就像這樣:
//而不是from'react-dom'import{useState}from'react';這就用到了開篇提到的變量__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED。
內部結構可以認為,當React團隊希望在react與「宿主環境對應的包」之間共享數據時,就會把他保存在這個神秘的內部變量中。
比如上文提到的,「Hook的具體實現」。
再比如,object.assign方法的polyfill,在react與react-dom中都會用到,但如果兩個包中分別引入,再分別打包,那麼polyfill的代碼會重複出現在react與react-dom兩個包中。
為了減少重複代碼,react會引入object.assign方法的polyfill,再將它保存在神秘的內部變量中。
react作為react-dom的peerDependencies,當項目中引入這兩個包後,react-dom內部使用的object.assign實際來自react:
//react-dom包內部constreact=require('react');const{assign}=react.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;常見問題了解了神秘的內部變量的作用,我們再來看看這種實現會造成的問題。
假設我們有2個項目:
組件庫項目A,負責開發組件
業務項目B,依賴A
B安裝依賴後,A會出現在B的node_modules中。
為了調試方便,我們用npm link功能將B中依賴的A由「B的node_modules中的A」改為「組件庫項目A」,
當npm link後,B中業務代碼使用的useState來自於「B的node_modules中的react」。
而B中引入的組件庫A的組件中使用的useState來自於「A的node_modules中的react」。
不同的react對應不同的__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,最終對應不同的react-dom。
這就會造成報錯。
解決辦法是在項目中為react增加別名(alias),使項目中所有用到react的地方都指向同一個react。
總結本文我們了解了react與react-dom中神秘的內部變量__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED的作用。
他能夠在這兩個包之間傳遞共享的數據。
需要注意的一點是,如果你也想用這種方式在兩個包之間共享數據,需要將其中一個包設為另一個包的peerDependencies。
否則,在打包時,「被共享的數據」只會在兩個包中分別存在一份。