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