您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關React#31的render怎么解決,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
卡頌日常從事基礎架構相關工作。這次接到一個任務:封裝一個React組件交給業務方使用。
組件本地開發無誤,自測無誤,交給業務方接入無誤,業務方測試環境驗收無誤。
然而,當包含該組件的頁面小流量上線后,監控平臺立刻收到報警:
Minified React error #31 ......
打開監控看板,發現大部分報錯來自于一款機型:「Vivo x7」,Android版本5.1。完整報錯信息如下:
看看時間:周五晚上6點半。呵,還有我解決不了的React問題?半小時搞定,過周末去~
然而......
簡單描述下#31號錯誤信息描述的內容:
React的render函數可接受的返回值類型包括:
string,比如return 'I am kasong';
number,比如return 123;
array,比如return [<p>ka</p>, <p>song</p>];。
其中[]會被處理為React.Fragment
object,比如return <p>ka song</p>;。
因為該返回值會被編譯為React.createElement(或jsx.createElement,視React版本不同而不同)。
而React.createElement的返回值是一個對象(即object類型)。
這里的報錯信息是說:你某個組件返回了一個非法值。因為這個值是object類型,但是他不是一個JSX對象。
想要復現這個問題也很簡單,比如如下代碼:
function App() { reutrn {}; }
返回值是個object,但非JSX對象。
作為React老司機,是絕對不可能寫錯返回值類型的。況且,寫錯的話,本地開發就會報錯了。
而且很奇怪,這個問題,為什么只在這款機型復現呢?
現在我們掌握的線索是:
這是個個別機型復現的報錯
報錯原因是因為render函數返回了錯誤的類型
我們需要更多線索!!
雖然是被壓縮的線上代碼,但好在React提供了必要的錯誤信息。
這個錯誤的object包含了如下字段:
found: object with keys {$$typeof, type, key, ref, props, _owner}).
居然包含$$typeof!!!
$$typeof是React源碼內部用來判斷JSX對象類型的字段。
比如React.Fragment、React.portal、div這三種JSX分別對應三種$$typeof。
換言之,包含$$typeof的object類型,大概率是一個JSX對象。
既然這個報錯的object對象就是一個JSX對象,那React為什么還認為他是一個非法的返回值呢?
React狠起來連自己都殺?
要解答這個問題,必須深入React源碼。
由于我的組件中沒有使用Fragment或Portal這樣的特性,所以將問題定位在普通React組件對應的$$typeof。
在源碼中,這種類型被稱為REACT_ELEMENT_TYPE。
PS:Fragment類型為REACT_FRAGMENT_TYPE,Portal類型為REACT_PORTAL_TYPE,
var hasSymbol = typeof Symbol === 'function' && Symbol.for; var REACT_ELEMENT_TYPE = hasSymbol ? Symbol.for('react.element') : 0xeac7;
可以看到,當宿主環境支持Symbol時,REACT_ELEMENT_TYPE === Symbol.for('react.element')。
不支持時,REACT_ELEMENT_TYPE === 0xeac7
與此同時,REACT_ELEMENT_TYPE這個變量的定義不僅存在于React這個包中,也存在于ReactDOM包中。
Vivo x7的webview原生不支持Symbol。似乎有點兒苗頭了!
審查業務方代碼后發現,業務方使用core-js實現了Symbol polyfill。
那么設想如下場景:
假如業務方代碼打包的順序是:
React -> core-js -> ReactDOM
那么在運行時,React首先加載,執行到定義REACT_ELEMENT_TYPE變量這行代碼時,宿主環境全局不存在Symbol。
所以在React這個包中,REACT_ELEMENT_TYPE === 0xeac7
接著運行core-js,他會在window上掛載Symbol。
接著ReactDOM在運行時,執行到定義REACT_ELEMENT_TYPE變量這行代碼時,宿主環境已經存在全局變量Symbol。
所以在ReactDOM中,REACT_ELEMENT_TYPE === Symbol.for('react.element')
而React.createElement方法來自React包,組件的render方法是在ReactDOM包中的reconciler模塊調用的,
所以就會發生判斷組件類型時,0xeac7 !== Symbol.for('react.element'),讓React認為這是個非法的返回值。
在遙遠的2016年,就有人就此給React提issue[1]。
然而審視業務方代碼后發現,依賴打包的順序是:
core-js -> React -> ReactDOM
按照剛才的推理,React和ReactDOM都能拿到core-js提供的Symbol polyfill。
此時早已華燈初上,我為我對React的輕視流下了不爭氣的淚水。
虧我還是React Contributor,React技術揭秘[2]作者,React 17的源碼方法名都能背下來。
嗯?React 17??難道!
v16.14之前版本的React中JSX對象會被編譯為React.createElement。
此版本之后createElement被從React包中拆分出來,獨立在react/jsx-runtime中。
編譯工作則由@babel/plugin-transform-react-jsx插件完成。
那么此時,REACT_ELEMENT_TYPE的定義存在于jsx-runtime、React、ReactDOM三個包中。
也就是說,如果編譯后包的執行順序是:
jsx-runtime -> core-js -> React -> ReactDOM
在低端Android機上還會復現這個問題!
這里截取一個網友遇到同樣問題后他的截圖:
這個問題的討論見Bug: IE 11 not working with latest React version
可以看到,jsx-runtime被babel編譯成第一個依賴,破案!
所以,這實際上是個babel編譯后產物順序造成的bug,已經有人給babel提相關issue[3]
最終我將JSX的編譯方式從編譯為jsx.createElement降級為React.createElement解決了這個報錯。
此時,夜已深。。。
每一片雪花都是那么純潔,直到他們組成了一場雪崩。
這個bug的各方,React、babel、提供組件的我、業務方代碼,單獨來看,沒有一方有問題。
但是,當一系列巧合合并在一起,就是一個線上bug。
這也顯示了線上小流量、報錯監控基建的重要性。
關于React#31的render怎么解決就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。