溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

React#31的render怎么解決

發(fā)布時(shí)間:2021-09-17 13:58:53 來源:億速云 閱讀:165 作者:柒染 欄目:web開發(fā)

這篇文章將為大家詳細(xì)講解有關(guān)React#31的render怎么解決,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對相關(guān)知識(shí)有一定的了解。

React#31的render怎么解決

卡頌日常從事基礎(chǔ)架構(gòu)相關(guān)工作。這次接到一個(gè)任務(wù):封裝一個(gè)React組件交給業(yè)務(wù)方使用。

組件本地開發(fā)無誤,自測無誤,交給業(yè)務(wù)方接入無誤,業(yè)務(wù)方測試環(huán)境驗(yàn)收無誤。

然而,當(dāng)包含該組件的頁面小流量上線后,監(jiān)控平臺(tái)立刻收到報(bào)警:

Minified React error #31 ......

打開監(jiān)控看板,發(fā)現(xiàn)大部分報(bào)錯(cuò)來自于一款機(jī)型:「Vivo x7」,Android版本5.1。完整報(bào)錯(cuò)信息如下:

React#31的render怎么解決

看看時(shí)間:周五晚上6點(diǎn)半。呵,還有我解決不了的React問題?半小時(shí)搞定,過周末去~

然而......

React#31的render怎么解決

找準(zhǔn)問題原因

簡單描述下#31號錯(cuò)誤信息描述的內(nèi)容:

React的render函數(shù)可接受的返回值類型包括:

  • string,比如return 'I am kasong';

  • number,比如return 123;

  • array,比如return  [<p>ka</p>, <p>song</p>];。

其中[]會(huì)被處理為React.Fragment

  • object,比如return  <p>ka song</p>;。

因?yàn)樵摲祷刂禃?huì)被編譯為React.createElement(或jsx.createElement,視React版本不同而不同)。

而React.createElement的返回值是一個(gè)對象(即object類型)。

這里的報(bào)錯(cuò)信息是說:你某個(gè)組件返回了一個(gè)非法值。因?yàn)檫@個(gè)值是object類型,但是他不是一個(gè)JSX對象。

想要復(fù)現(xiàn)這個(gè)問題也很簡單,比如如下代碼:

function App() {   reutrn {}; }

返回值是個(gè)object,但非JSX對象。

作為React老司機(jī),是絕對不可能寫錯(cuò)返回值類型的。況且,寫錯(cuò)的話,本地開發(fā)就會(huì)報(bào)錯(cuò)了。

而且很奇怪,這個(gè)問題,為什么只在這款機(jī)型復(fù)現(xiàn)呢?

初見端倪

現(xiàn)在我們掌握的線索是:

  • 這是個(gè)個(gè)別機(jī)型復(fù)現(xiàn)的報(bào)錯(cuò)

  • 報(bào)錯(cuò)原因是因?yàn)閞ender函數(shù)返回了錯(cuò)誤的類型

我們需要更多線索!!

 React#31的render怎么解決

雖然是被壓縮的線上代碼,但好在React提供了必要的錯(cuò)誤信息。

這個(gè)錯(cuò)誤的object包含了如下字段:

found: object with keys {$$typeof, type, key, ref, props, _owner}).

居然包含$$typeof!!!

$$typeof是React源碼內(nèi)部用來判斷JSX對象類型的字段。

比如React.Fragment、React.portal、div這三種JSX分別對應(yīng)三種$$typeof。

換言之,包含$$typeof的object類型,大概率是一個(gè)JSX對象。

既然這個(gè)報(bào)錯(cuò)的object對象就是一個(gè)JSX對象,那React為什么還認(rèn)為他是一個(gè)非法的返回值呢?

 React#31的render怎么解決

React狠起來連自己都?xì)?

深入源碼

要解答這個(gè)問題,必須深入React源碼。

由于我的組件中沒有使用Fragment或Portal這樣的特性,所以將問題定位在普通React組件對應(yīng)的$$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;

可以看到,當(dāng)宿主環(huán)境支持Symbol時(shí),REACT_ELEMENT_TYPE === Symbol.for('react.element')。

不支持時(shí),REACT_ELEMENT_TYPE === 0xeac7

與此同時(shí),REACT_ELEMENT_TYPE這個(gè)變量的定義不僅存在于React這個(gè)包中,也存在于ReactDOM包中。

Vivo x7的webview原生不支持Symbol。似乎有點(diǎn)兒苗頭了!

審查業(yè)務(wù)方代碼后發(fā)現(xiàn),業(yè)務(wù)方使用core-js實(shí)現(xiàn)了Symbol polyfill。

那么設(shè)想如下場景:

假如業(yè)務(wù)方代碼打包的順序是:

React -> core-js -> ReactDOM

那么在運(yùn)行時(shí),React首先加載,執(zhí)行到定義REACT_ELEMENT_TYPE變量這行代碼時(shí),宿主環(huán)境全局不存在Symbol。

所以在React這個(gè)包中,REACT_ELEMENT_TYPE === 0xeac7

接著運(yùn)行core-js,他會(huì)在window上掛載Symbol。

接著ReactDOM在運(yùn)行時(shí),執(zhí)行到定義REACT_ELEMENT_TYPE變量這行代碼時(shí),宿主環(huán)境已經(jīng)存在全局變量Symbol。

所以在ReactDOM中,REACT_ELEMENT_TYPE === Symbol.for('react.element')

而React.createElement方法來自React包,組件的render方法是在ReactDOM包中的reconciler模塊調(diào)用的,

所以就會(huì)發(fā)生判斷組件類型時(shí),0xeac7 !== Symbol.for('react.element'),讓React認(rèn)為這是個(gè)非法的返回值。

在遙遠(yuǎn)的2016年,就有人就此給React提issue[1]。

事實(shí)真的如此么?

然而審視業(yè)務(wù)方代碼后發(fā)現(xiàn),依賴打包的順序是:

core-js -> React -> ReactDOM

按照剛才的推理,React和ReactDOM都能拿到core-js提供的Symbol polyfill。

React#31的render怎么解決

撥云見日

此時(shí)早已華燈初上,我為我對React的輕視流下了不爭氣的淚水。

虧我還是React Contributor,React技術(shù)揭秘[2]作者,React 17的源碼方法名都能背下來。

嗯?React 17??難道!

React#31的render怎么解決

v16.14之前版本的React中JSX對象會(huì)被編譯為React.createElement。

此版本之后createElement被從React包中拆分出來,獨(dú)立在react/jsx-runtime中。

編譯工作則由@babel/plugin-transform-react-jsx插件完成。

那么此時(shí),REACT_ELEMENT_TYPE的定義存在于jsx-runtime、React、ReactDOM三個(gè)包中。

也就是說,如果編譯后包的執(zhí)行順序是:

jsx-runtime -> core-js -> React -> ReactDOM

在低端Android機(jī)上還會(huì)復(fù)現(xiàn)這個(gè)問題!

這里截取一個(gè)網(wǎng)友遇到同樣問題后他的截圖:

React#31的render怎么解決

  • 這個(gè)問題的討論見Bug: IE 11 not working with latest React version

可以看到,jsx-runtime被babel編譯成第一個(gè)依賴,破案!

所以,這實(shí)際上是個(gè)babel編譯后產(chǎn)物順序造成的bug,已經(jīng)有人給babel提相關(guān)issue[3]

最終我將JSX的編譯方式從編譯為jsx.createElement降級為React.createElement解決了這個(gè)報(bào)錯(cuò)。

此時(shí),夜已深。。。

每一片雪花都是那么純潔,直到他們組成了一場雪崩。

這個(gè)bug的各方,React、babel、提供組件的我、業(yè)務(wù)方代碼,單獨(dú)來看,沒有一方有問題。

但是,當(dāng)一系列巧合合并在一起,就是一個(gè)線上bug。

這也顯示了線上小流量、報(bào)錯(cuò)監(jiān)控基建的重要性。

關(guān)于React#31的render怎么解決就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI