大家好,我卡頌。
不知道大家在用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包內部
const react = 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
。
否則,在打包時,被共享的資料只會在兩個包中分別存在一份。