React懶加載實(shí)現(xiàn)原理深入分析
1.代碼分割
(1)為什么要進(jìn)行代碼分割?
現(xiàn)在前端項(xiàng)目基本都采用打包技術(shù),比如 Webpack,JS邏輯代碼打包后會(huì)產(chǎn)生一個(gè) bundle.js 文件,而隨著我們引用的第三方庫(kù)越來(lái)越多或業(yè)務(wù)邏輯代碼越來(lái)越復(fù)雜,相應(yīng)打包好的 bundle.js 文件體積就會(huì)越來(lái)越大,因?yàn)樾枰日?qǐng)求加載資源之后,才會(huì)渲染頁(yè)面,這就會(huì)嚴(yán)重影響到頁(yè)面的首屏加載。
而為了解決這樣的問(wèn)題,避免大體積的代碼包,我們則可以通過(guò)技術(shù)手段對(duì)代碼包進(jìn)行分割,能夠創(chuàng)建多個(gè)包并在運(yùn)行時(shí)動(dòng)態(tài)地加載。現(xiàn)在像 Webpack、 Browserify等打包器都支持代碼分割技術(shù)。
(2)什么時(shí)候應(yīng)該考慮進(jìn)行代碼分割?
這里舉一個(gè)平時(shí)開(kāi)發(fā)中可能會(huì)遇到的場(chǎng)景,比如某個(gè)體積相對(duì)比較大的第三方庫(kù)或插件(比如JS版的PDF預(yù)覽庫(kù))只在單頁(yè)應(yīng)用(SPA)的某一個(gè)不是首頁(yè)的頁(yè)面使用了,這種情況就可以考慮代碼分割,增加首屏的加載速度。
2.React的懶加載
示例代碼:
import React, { Suspense } from 'react';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}> <OtherComponent />
</Suspense>
</div>
);
}如上代碼中,通過(guò) import()、React.lazy 和 Suspense 共同一起實(shí)現(xiàn)了 React 的懶加載,也就是我們常說(shuō)了運(yùn)行時(shí)動(dòng)態(tài)加載,即 OtherComponent 組件文件被拆分打包為一個(gè)新的包(bundle)文件,并且只會(huì)在 OtherComponent 組件渲染時(shí),才會(huì)被下載到本地。
那么上述中的代碼拆分以及動(dòng)態(tài)加載究竟是如何實(shí)現(xiàn)的呢?讓我們來(lái)一起探究其原理是怎樣的。
import() 原理
import() 函數(shù)是由TS39提出的一種動(dòng)態(tài)加載模塊的規(guī)范實(shí)現(xiàn),其返回是一個(gè) promise。在瀏覽器宿主環(huán)境中一個(gè)import()的參考實(shí)現(xiàn)如下:
function import(url) {
return new Promise((resolve, reject) => {
const script = document.createElement("script");
const tempGlobal = "__tempModuleLoadingVariable" + Math.random().toString(32).substring(2);
script.type = "module";
script.textContent = `import * as m from "${url}"; window.${tempGlobal} = m;`;
script.onload = () => {
resolve(window[tempGlobal]);
delete window[tempGlobal];
script.remove();
};
script.onerror = () => {
reject(new Error("Failed to load module script with URL " + url));
delete window[tempGlobal];
script.remove();
};
document.documentElement.appendChild(script);
});
}當(dāng) Webpack 解析到該import()語(yǔ)法時(shí),會(huì)自動(dòng)進(jìn)行代碼分割。
React.lazy 原理
以下 React 源碼基于 16.8.0 版本
React.lazy 的源碼實(shí)現(xiàn)如下:
export function lazy<T, R>(ctor: () => Thenable<T, R>): LazyComponent<T> {
let lazyType = {
?typeof: REACT_LAZY_TYPE,
_ctor: ctor,
// React uses these fields to store the result.
_status: -1,
_result: null,
};
return lazyType;
}可以看到其返回了一個(gè) LazyComponent 對(duì)象。
而對(duì)于 LazyComponent 對(duì)象的解析:
...
case LazyComponent: {
const elementType = workInProgress.elementType;
return mountLazyComponent(
current,
workInProgress,
elementType,
updateExpirationTime,
renderExpirationTime,
);
}
......
case LazyComponent: {
const elementType = workInProgress.elementType;
return mountLazyComponent(
current,
workInProgress,
elementType,
updateExpirationTime,
renderExpirationTime,
);
}
...// Pending = 0, Resolved = 1, Rejected = 2
export function readLazyComponentType<T>(lazyComponent: LazyComponent<T>): T {
const status = lazyComponent._status;
const result = lazyComponent._result;
switch (status) {
case Resolved: {
const Component: T = result;
return Component;
}
case Rejected: {
const error: mixed = result;
throw error;
}
case Pending: {
const thenable: Thenable<T, mixed> = result;
throw thenable;
}
default: { // lazyComponent 首次被渲染
lazyComponent._status = Pending;
const ctor = lazyComponent._ctor;
const thenable = ctor();
thenable.then(
moduleObject => {
if (lazyComponent._status === Pending) {
const defaultExport = moduleObject.default;
lazyComponent._status = Resolved;
lazyComponent._result = defaultExport;
}
},
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;
}
}
}
參考React實(shí)戰(zhàn)視頻講解:進(jìn)入學(xué)習(xí)
注:如果 readLazyComponentType 函數(shù)多次處理同一個(gè) lazyComponent,則可能進(jìn)入Pending、Rejected等 case 中。
從上述代碼中可以看出,對(duì)于最初 React.lazy() 所返回的 LazyComponent 對(duì)象,其 _status 默認(rèn)是 -1,所以首次渲染時(shí),會(huì)進(jìn)入 readLazyComponentType 函數(shù)中的 default 的邏輯,這里才會(huì)真正異步執(zhí)行 import(url)操作,由于并未等待,隨后會(huì)檢查模塊是否 Resolved,如果已經(jīng)Resolved了(已經(jīng)加載完畢)則直接返回moduleObject.default(動(dòng)態(tài)加載的模塊的默認(rèn)導(dǎo)出),否則將通過(guò) throw 將 thenable 拋出到上層。
為什么要 throw 它?這就要涉及到 Suspense 的工作原理,我們接著往下分析。
Suspense 原理
由于 React 捕獲異常并處理的代碼邏輯比較多,這里就不貼源碼,感興趣可以去看 throwException 中的邏輯,其中就包含了如何處理捕獲的異常。簡(jiǎn)單描述一下處理過(guò)程,React 捕獲到異常之后,會(huì)判斷異常是不是一個(gè) thenable,如果是則會(huì)找到 SuspenseComponent ,如果 thenable 處于 pending 狀態(tài),則會(huì)將其 children 都渲染成 fallback 的值,一旦 thenable 被 resolve 則 SuspenseComponent 的子組件會(huì)重新渲染一次。
為了便于理解,我們也可以用 componentDidCatch 實(shí)現(xiàn)一個(gè)自己的 Suspense 組件,如下:
class Suspense extends React.Component {
state = {
promise: null
}
componentDidCatch(err) {
// 判斷 err 是否是 thenable
if (err !== null && typeof err === 'object' && typeof err.then === 'function') {
this.setState({ promise: err }, () => {
err.then(() => {
this.setState({
promise: null
})
})
})
}
}
render() {
const { fallback, children } = this.props
const { promise } = this.state
return <>{ promise ? fallback : children }</>
}
}小結(jié)
至此,我們分析完了 React 的懶加載原理。簡(jiǎn)單來(lái)說(shuō),React利用 React.lazy與import()實(shí)現(xiàn)了渲染時(shí)的動(dòng)態(tài)加載 ,并利用Suspense來(lái)處理異步加載資源時(shí)頁(yè)面應(yīng)該如何顯示的問(wèn)題。
到此這篇關(guān)于React懶加載實(shí)現(xiàn)原理深入分析的文章就介紹到這了,更多相關(guān)React懶加載內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React組件內(nèi)事件傳參實(shí)現(xiàn)tab切換的示例代碼
本篇文章主要介紹了React組件內(nèi)事件傳參實(shí)現(xiàn)tab切換的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-07-07
jenkins分環(huán)境部署vue/react項(xiàng)目的方法步驟
這篇文章主要介紹了jenkins分環(huán)境部署vue/react項(xiàng)目的方法,本文分步驟給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-02-02
react時(shí)間分片實(shí)現(xiàn)流程詳解
實(shí)現(xiàn)react時(shí)間分片,主要內(nèi)容包括什么是時(shí)間分片、為什么需要時(shí)間分片、實(shí)現(xiàn)分片開(kāi)啟 - 固定、實(shí)現(xiàn)分片中斷、重啟 - 連續(xù)、分片重啟、實(shí)現(xiàn)延遲執(zhí)行 - 有間隔、時(shí)間分片異步執(zhí)行方案的演進(jìn)、時(shí)間分片簡(jiǎn)單實(shí)現(xiàn)、總結(jié)、基本概念、基礎(chǔ)應(yīng)用、原理機(jī)制和需要注意的事項(xiàng)等2022-11-11
React-RouterV6+AntdV4實(shí)現(xiàn)Menu菜單路由跳轉(zhuǎn)的方法
這篇文章主要介紹了React-RouterV6+AntdV4實(shí)現(xiàn)Menu菜單路由跳轉(zhuǎn),主要有兩種跳轉(zhuǎn)方式一種是編程式跳轉(zhuǎn)另一種是NavLink鏈接式跳轉(zhuǎn),每種方式通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08
flouting?ui定位組件完美替代ant?deisgn使用詳解
這篇文章主要為大家介紹了flouting?ui定位組件完美替代ant?deisgn使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11
antd中form表單的wrapperCol和labelCol問(wèn)題詳解
最近學(xué)習(xí)中遇到了些問(wèn)題,所以給大家總結(jié),下面這篇文章主要給大家介紹了關(guān)于antd中form表單的wrapperCol和labelCol問(wèn)題的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-02-02

