react.lazy 路由懶加載_React lazy/Suspense使用及源碼解析

React v16.6.0已經發布快一年了,為保障項目迭代發布,沒有及時更新react版本,最近由于開啟了新項目,于是使用新的react版本進行了項目開發。項目工程如何搭建,如何滿足兼容性要求,如何規范化等等這里不作為介紹重點,這里想說一下react的lazy,suspense,這塊在react官網作為code-splitting重點說明過,可見其出現的意義。

官網寫的比較詳細,總結起來就是,如果你項目中使用webpack或browserify進行打包,隨著工程項目的增長和大量三方庫的引入,會使你打包后的文件逐漸變大,用戶加載文件時,會花大量時間去加載他們并不關心的內容,而此時,懶加載React.lazy的概念就應運而生。

注意:官網提示React.lazy并不適合SSR

這里介紹基于路由的懶加載,也是比較常用的方式,React.lazy只要一句話就能實現,如下:

const OtherComponent = React.lazy(async () => import('./OtherComponent'));

lazy中的函數返回Promise對象,引入了導出React Component的文件,并且官方提示為了有過度效果,還提供了Suspense組件,而且如果不引入的話還會報錯,如下:

<Suspense fallback={<div>Loading...</div>}><OtherComponent />
</Suspense>

以上都是官網示例,在項目實際使用中,還沒有單獨對功能組件進行懶加載,可以依據業務租組件的復雜度決定是否使用懶加載,個人覺得路由的懶加載是有必要的。使用中我們用高階組件進行Suspense的封裝:

const WithLazyLoad = (WrappedComponent: React.ComponentType<any>) =>class HOC extends React.Component {private displayName = `HOC(${getDisplayName(WrappedComponent)})`;public render() {console.log(this.displayName)return (<React.Suspense fallback={<div>Loading...</div>} ><WrappedComponent {...this.props} /></React.Suspense>  )}};

在App.tsx中對路由進行定義,這里假設有三個路由地址:

const About = React.lazy(() => import('./components/About/About'));
const Hello = React.lazy(() => import('./components/Hello/Hello'));
const Home = React.lazy(() => import('./components/Home/Home'));class App extends React.Component {public render() {return (<BrowserRouter><Switch><Route path="/" exact={true} component={WithLazyLoad(Hello)}  /><Route path="/home" exact={true} component={WithLazyLoad(Home)} /><Route path="/about" exact={true} component={WithLazyLoad(About)} /></Switch></BrowserRouter>);}
}

以上兩步,就完成了基本功能對實現,我們來看下效果

v2-3f502daa07913add3276195b60fb3cca_b.png
使用Lazy

使用lazy后會根據路由打包成多個chunk文件,進行按需加載。我們打印懶加載的組件信息,返回的是個對象,示意如下:

v2-33c2606efaec6bad6535ff66ad31da8a_b.jpg
React.lazy(() =&amp;gt; import(&amp;#39;./components/Home/Home&amp;#39;))返回對象
主要屬性說明:
$$typeof:對象類型,包括Symbol(react.lazy)、Symbol(react.element)、Symbol(react.portal)等等,在react源碼中有定義
_ctor:懶加載異步函數,返回Promise對象,即 async () => import('./Home')
_result:存儲懶加載異步函數執行的結果,可能值為error、moduleObject.default(即? Home())
_status:當前狀態,初始值(-1)、Pending(0)、Resolved(1)、Rejected(2)

查看react源碼,在react-dom.js文件下的beginWork函數中,可以看到LazyComponent的加載方式其實是調用了mountLazyComponent函數,

switch (workInProgress.tag) {// ...case LazyComponent:{var _elementType = workInProgress.elementType;return mountLazyComponent(current$$1, workInProgress, _elementType, updateExpirationTime, renderExpirationTime);}// ...
}

查看mountLazyComponent函數,最重要的地方是,下面會分步解析:

// 解析lazy component
var Component = readLazyComponentType(elementType);
// Store the unwrapped component in the type.
workInProgress.type = Component;
// 獲取Component類型,可能值ClassComponent、FunctionComponent、ForwardRef、MemoComponent、IndeterminateComponent
var resolvedTag = workInProgress.tag = resolveLazyComponentTag(Component);
// 初始化props
var resolvedProps = resolveDefaultProps(Component, props);

首先看readLazyComponentType函數,其參數elementType為上面打印出的對象,返回懶加載的組件,下面列出了關鍵代碼,_thenable執行ctor()異步函數,拿到import的組件函數即f home(),拿到后暫存于workInProgress.type:

function readLazyComponentType(lazyComponent) {var status = lazyComponent._status;var result = lazyComponent._result;switch (status) {// ...default:{lazyComponent._status = Pending;var ctor = lazyComponent._ctor;var _thenable = ctor();_thenable.then(function (moduleObject) {if (lazyComponent._status === Pending) {var defaultExport = moduleObject.default;{if (defaultExport === undefined) {warning$1(false, 'lazy: Expected the result of a dynamic import() call. ' + 'Instead received: %snnYour code should look like: n  ' + "const MyComponent = lazy(() => import('./MyComponent'))", moduleObject);}}lazyComponent._status = Resolved;lazyComponent._result = defaultExport;}}, function (error) {if (lazyComponent._status === Pending) {lazyComponent._status = Rejected;lazyComponent._result = error;}});// Handle synchronous thenables.switch (lazyComponent._status) {case Resolved:return lazyComponent._result;case Rejected:throw lazyComponent._result;}lazyComponent._result = _thenable;throw _thenable;}}
}

v2-5cb0174e36850cc0ba9c4b47c3e098a1_b.jpg
正常返回的lazyComponent._result

隨后執行resolveLazyComponentTag函數,入參為readLazyComponentType拿到的結果Component,由于我們的返回的是f home(),所以直接用shouldConstruct判斷Component的原型上是否有isReactComponent,如果存在則為class組件,否則為函數組件,代碼如下:

function resolveLazyComponentTag(Component) {if (typeof Component === 'function') {return shouldConstruct(Component) ? ClassComponent : FunctionComponent;} else if (Component !== undefined && Component !== null) {var $$typeof = Component.$$typeof;if ($$typeof === REACT_FORWARD_REF_TYPE) {return ForwardRef;}if ($$typeof === REACT_MEMO_TYPE) {return MemoComponent;}}return IndeterminateComponent;
}

之后執行resolveDefaultProps,初始化默認的props

function resolveDefaultProps(Component, baseProps) {if (Component && Component.defaultProps) {// Resolve default props. Taken from ReactElementvar props = _assign({}, baseProps);var defaultProps = Component.defaultProps;for (var propName in defaultProps) {if (props[propName] === undefined) {props[propName] = defaultProps[propName];}}return props;}return baseProps;
}

執行完上面的方法,懶加載的前期工作就差不多完成了,下面根據resolvedTag進行組件刷新,我們這里是ClassComponent,所以重點看這塊的更新方法updateClassComponent,下面我們逐段分析該方法

switch (resolvedTag) {// ...case ClassComponent:{child = updateClassComponent(null, workInProgress, Component, resolvedProps, renderExpirationTime);break;}// ...}

updateClassComponent方法首先做了propTypes的校驗(如果在組件中設置了的話),注意無法在CreateElement中驗證lazy組件的屬性,只能在updateClassComponent中進行驗證。

{if (workInProgress.type !== workInProgress.elementType) {var innerPropTypes = Component.propTypes;if (innerPropTypes) {checkPropTypes(innerPropTypes, nextProps, // Resolved props'prop', getComponentName(Component), getCurrentFiberStackInDev);}}}

然后檢查是否有context,如果有的話則設置Provider,并監聽變化,隨后執行實例化,最后執行finishClassComponent方法,進行Component的render,即CreateElement,渲染到dom上

 var hasContext = void 0;if (isContextProvider(Component)) {hasContext = true;pushContextProvider(workInProgress);} else {hasContext = false;}prepareToReadContext(workInProgress, renderExpirationTime);// ...constructClassInstance(workInProgress, Component, nextProps, renderExpirationTime);mountClassInstance(workInProgress, Component, nextProps, renderExpirationTime);
// ...var nextUnitOfWork = finishClassComponent(current$$1, workInProgress, Component, shouldUpdate, hasContext, renderExpirationTime);

Suspense組件的渲染方式類似,也是用updateSuspenseComponent,只不過里面有nextDidTimeout標志,決定是渲染fallback還是其子組件。

上面就是關于React.lazy的一些想要分享和記錄的一些內容,如果存在錯誤的理解或更好的理解方式,希望多多交流

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/394726.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/394726.shtml
英文地址,請注明出處:http://en.pswp.cn/news/394726.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Dart編程語言入門

Dart基礎入門語法介紹&#xff0c;詳細說明可以查看相關視頻《Dart編程語言入門》。 變量與常量 變量 1.使用 var 聲明變量,默認值為 null var a;//null a 10;2.顯示類型聲明 int a;//null a 10;3.使用 var 聲明&#xff0c;可賦予不同類型的值 var a; //null a 10; //int a…

《PHP精粹:編寫高效PHP代碼》——1.1節為什么要使用面向對象編程

本節書摘來自華章社區《PHP精粹&#xff1a;編寫高效PHP代碼》一書中的第1章&#xff0c;第1.1節為什么要使用面向對象編程&#xff0c;作者&#xff1a;&#xff08;美&#xff09;  Davey Shafik&#xff0c;更多章節內容可以訪問云棲社區“華章社區”公眾號查看 1.1 為什…

c語言數據結構系統化,C語言數據結構+數據庫+操作系統

http://cv.qiaobutang.com/post/55c419b20cf2009bd4607795第二部分是專業相關的C &#xff0c;數據庫&#xff0c;操作系統&#xff0c;數據結構。http://c.biancheng.net/cpp/u/shuju/數據(Data)是信息的載體&#xff0c;它能夠被計算機識別、存儲和加工處理。它是計算機程序加…

c語言判斷一個序列是不是另一個的子序列

1 #include <stdio.h>2 #include <string.h>//添加字符串頭文件3 4 int Subsequence(char s[], char t[]) 5 {6 int m,n,i,j;7 n strlen(s); //n表示序列S的長度8 m strlen(t); //m表示序列T的長度9 i0; 10 j0; 11 if (m>…

linux中python如何調用matlab的數據_特征錦囊:如何在Python中處理不平衡數據

今日錦囊特征錦囊&#xff1a;如何在Python中處理不平衡數據? Index1、到底什么是不平衡數據2、處理不平衡數據的理論方法3、Python里有什么包可以處理不平衡樣本4、Python中具體如何處理失衡樣本印象中很久之前有位朋友說要我寫一篇如何處理不平衡數據的文章&#xff0c;整理…

源碼安裝zabbix遇到的報錯集錦

報錯1&#xff1a;checking for mysql_config... configure: error: MySQL library not found 解決辦法&#xff1a;查找mysql_config #find / -name "mysql_config*" /usr/local/mysql/bin/mysql_config 在配置時將原有的 --with-mysql 改為 --with-mysql/usr/loca…

pso算法c++語言代碼,一C++PSO(PSO)算法

收集和變化PSO算法&#xff0c;它可用于參考實施&#xff1a;#include #include #include #include #include #define rand_01 ((float)rand() / (float)RAND_MAX)const int numofdims 30;const int numofparticles 50;using namespace std;//typedef void (*FitnessFunc)(fl…

Hadoop不適合哪些場景 哪些場景適合?

Hadoop設計的目的主要包括下面幾個方面&#xff0c;也就是所謂的適用場景&#xff1a; 1&#xff1a;超大文件 可以是幾百M&#xff0c;幾百T這個級別的文件。 2&#xff1a;流式數據訪問 Hadoop適用于一次寫入&#xff0c;多次讀取的場景&#xff0c;也就是數據復制進去之后&a…

微服務 邊界服務_遵循這些實用原則以獲取精心設計的微服務邊界

微服務 邊界服務by Jake Lumetta杰克盧米塔(Jake Lumetta) 遵循這些實用原則以獲取精心設計的微服務邊界 (Follow these practical principles to get well-designed microservices boundaries) 如何避免使微服務太小和緊密耦合 (How to avoid making your microservices too …

ShareEntryActivity java.lang.ClassNotFoundException | Android類找不到問題

錯誤堆棧&#xff1a; Process: com.mci.smagazine, PID: 23265java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.mci.smagazine/com.mci.smagazine.apshare.ShareEntryActivity}: java.lang.ClassNotFoundException: com.mci.smagazine.apshare…

阿里Android p6準備,項目經歷準備篇——如何準備阿里巴巴P6/P7前端面試

項目經歷準備篇——如何準備阿里巴巴P6/P7前端面試在上次的校招文章之后&#xff0c;有很多同學問有沒有社招相關的東西可以寫一篇&#xff0c;現在它來了。比起校招&#xff0c;社招更加看重項目經歷項目經歷反應的思考。本文針對的是想進入阿里的P6/P7同學&#xff0c;著重講…

for in for of區別_Python 第4課:for…in循環黃金搭檔之range()函數

樂學趣學Py● 04&#xff1a;for…in循環黃金搭檔之range()函數●Python趣味小百科Python中的繪圖模塊為什么叫Turtle海龜&#xff0c;而不是cat ,dog,bird呢&#xff1f;原來Python引用了麻省理工大學教授開發的logo海龜制圖語言,能通過繪圖直觀地教大家學習編程。實踐是最好的…

《游戲設計師修煉之道:數據驅動的游戲設計》一3.8小結

3.8小結 在玩游戲期間使用的數學知識通常相當簡單&#xff0c;盡管代碼中使用的數學知識可能非常復雜。玩家不希望由于在玩游戲期間不得不處理許多數字而分心&#xff0c;因為他們的大腦必須從控制角色的動作轉換到記住數字的含義。許多游戲回避了數字&#xff0c;而是通過像計…

ubuntu下安裝配置nfs

sudo apt-get install nfs-kernel-server sudo /nfs_root vim /etc/exports 在這個文件末尾添加 /nfs_root *(rw,sync,no_root_squash) 保存退出 重啟nfs服務 sudo /etc/init.d/rpcbind restart sudo /etc/init.d/nfs-kernel-server restart 測試 sudo mount 192.168.2.1:/nf…

使命愿景價值觀_為什么在制作產品時應該專注于愿景,價值,風險和先例

使命愿景價值觀by Steve史蒂夫(Steve) 為什么在制作產品時應該專注于愿景&#xff0c;價值&#xff0c;風險和先例 (Why you should focus on vision, value, risk, and precedent when making your product) 幾周前&#xff0c;產品開發人員John Cutler發表了一篇出色的文章&…

安卓前端布局Android,Android開發的幾種常見布局

目前正在從事iOS開發&#xff0c;對于安卓就是大學的時候自學了點&#xff0c;做過幾個小的項目&#xff0c;軟件外包大賽、計算機設計大賽、移動應用大賽都拿過獎項&#xff0c;呵呵。。。現在回想起來以前大學做的安卓比賽是多么的幼稚。 從現在開始我要從頭一步一步回顧安卓…

《Cocos2D權威指南》——3.9 本章小結

3.9 本章小結 本章對Cocos2D中的幾個核心類&#xff08;CCNode、CCScene、CCLayer、CCSprite&#xff09;進行了詳細介紹&#xff0c;并且通過節點層級圖讓大家了解到Cocos2D游戲的基本組成&#xff1b;然后介紹了Cocos2D中的單例。通過完善第2章的游戲實例&#xff0c;大家對…

永恒python圖片_python 數據詞云展示實例(3)- 背景圖設置

記錄wordcloud庫背景圖的設置及樣板 之前介紹了wordcloud的基本使用wordcloud的基本使用&#xff0c;本文記錄一下如何設置背景圖。 樣圖 背景圖tim.jpg 生成樣圖dream.png 樣板 from PIL import Image,ImageSequence image Image.open(tim.jpg)#打開背景圖 graph np.array(im…

創造的快樂

早上9點半到的圖書館&#xff0c;十點左右才進入狀態&#xff0c;上午和下午的一半時間都用來看AMD的GCN架構&#xff0c;看這種官方的文檔&#xff0c;和論文一樣&#xff0c;只看摘要和圖片&#xff0c;沒有死磕的精神&#xff0c;很難有收獲&#xff0c;結果就是&#xff0c…

python心得-基本概念2

一 編程語言介紹 1.1 機器語言&#xff1a;直接用計算機能理解的二進制指令編寫程序&#xff0c;直接控制硬件 1.2 匯編語言&#xff1a;用英文標簽取代二進制指令取編寫程序&#xff0c;本質也是在直接控制硬件 1.3 高級語言&#xff1a;用人能理解的表達方式去編寫程序&#…