學習本文的前置知識:
-
每次更新都會從React
(根rootFiber
節點)向下深度優先周遊Fiber
-
在編譯時會變為JSX
,在元件React.createElement
時會調用該方法。render
30秒速答:
知乎首頁是
React
寫的,我們可以覆寫
React.createElement
方法,在運作時将所有
div
節點渲染為
React.Fragment
。
這樣就能清除所有
div
。
讓我們來愉快的改造
知乎
吧。
拿到React對象
這時候遇到了第一個問題:
知乎
沒把
React
暴露到全局(廢話,當然不會),怎麼擷取
React
對象呢?
好在當我們使用
React Dev Tools
時,
Dev Tools
會向頁面注入全局變量
__REACT_DEVTOOLS_GLOBAL_HOOK__
。
這個變量是連接配接
React
與
Dev Tools
的橋梁。
其中
renderers
屬性指頁面使用的
渲染器
。
React
源碼架構劃分為
排程器 - 協調器 - 渲染器
。
對于不同的宿主環境,使用不同的
渲染器
。
- 對于
,使用web
ReactDOM渲染器
- 對于
,使用用戶端
React-Native渲染器
在
renderers
屬性中,我們發現一個方法
findFiberByHostInstance
:
方法名居然出現了
Fiber
字樣。
Fiber節點
是
React
的最小可排程單元,可以了解為
虛拟DOM節點
。
那
findFiberByHostInstance
方法所在檔案一定有
React
相關定義。我們右鍵跳轉到定義函數的檔案,
在檔案内搜
.createElement
果然讓我們找到了。打上斷點,重新整理頁面試試
果然進來了,事情越發有趣了。
看看
o.a
包含的屬性,
Children
、
createElement
......
看來這就是
React
對象了。
我們将來之不易的
React
對象儲存在
window
,順便把
React.createElement
也儲存一份。
現在放開斷點,
window.React
已經指向知乎首頁内部使用的React啦。
修改React.createElement
React.createElement
方法第一個參數為
type
。
在源碼中,我們找到
React.Fragment
對應的
type
。
if (typeof Symbol === 'function' && Symbol.for) {
const symbolFor = Symbol.for;
REACT_ELEMENT_TYPE = symbolFor('react.element');
REACT_PORTAL_TYPE = symbolFor('react.portal');
REACT_FRAGMENT_TYPE = symbolFor('react.fragment');
REACT_STRICT_MODE_TYPE = symbolFor('react.strict_mode');
REACT_PROFILER_TYPE = symbolFor('react.profiler');
REACT_PROVIDER_TYPE = symbolFor('react.provider');
// ...
接下來,修改全局變量,将所有
div
變為
Fragment
。
React.createElement = (type, ...args) => {
if (type === 'div') {
type = Symbol.for('react.fragment');
}
// originCreateElement是原始React.createElement
return originCreateElement(type, ...args);
}
讓我們滾動頁面,觸發随便啥元件的
setState
。
接下來,就是見證奇迹的時刻。。。
div
都消失啦,終于恢複了往日的清爽界面(大誤)
理論上我們可以用這個方法将任何
React
應用改造成任何樣子。