React:不要動,否則你會被炒魷魚

卡頌發表於2022-07-14

大家好,我卡頌。

不知道大家在用React開發時,有沒有注意到reactreact-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,在reactreact-dom中都會用到,但如果兩個包中分別引入,再分別打包,那麼polyfill的程式碼會重複出現在reactreact-dom兩個包中。

為了減少重複程式碼,react會引入object.assign方法的polyfill,再將它儲存在神祕的內部變數中。

react作為react-dompeerDependencies,當專案中引入這兩個包後,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

總結

本文我們瞭解了reactreact-dom中神祕的內部變數__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED的作用。

他能夠在這兩個包之間傳遞共享的資料。

需要注意的一點是,如果你也想用這種方式在兩個包之間共享資料,需要將其中一個包設為另一個包的peerDependencies

否則,在打包時,被共享的資料只會在兩個包中分別存在一份。

相關文章