react.lazy 路由懒加载_React lazy/Suspense使用及源码解析
React v16.6.0已經(jīng)發(fā)布快一年了,為保障項目迭代發(fā)布,沒有及時更新react版本,最近由于開啟了新項目,于是使用新的react版本進行了項目開發(fā)。項目工程如何搭建,如何滿足兼容性要求,如何規(guī)范化等等這里不作為介紹重點,這里想說一下react的lazy,suspense,這塊在react官網(wǎng)作為code-splitting重點說明過,可見其出現(xiàn)的意義。
官網(wǎng)寫的比較詳細,總結(jié)起來就是,如果你項目中使用webpack或browserify進行打包,隨著工程項目的增長和大量三方庫的引入,會使你打包后的文件逐漸變大,用戶加載文件時,會花大量時間去加載他們并不關(guān)心的內(nèi)容,而此時,懶加載React.lazy的概念就應(yīng)運而生。
注意:官網(wǎng)提示React.lazy并不適合SSR
這里介紹基于路由的懶加載,也是比較常用的方式,React.lazy只要一句話就能實現(xiàn),如下:
const OtherComponent = React.lazy(async () => import('./OtherComponent'));lazy中的函數(shù)返回Promise對象,引入了導(dǎo)出React Component的文件,并且官方提示為了有過度效果,還提供了Suspense組件,而且如果不引入的話還會報錯,如下:
<Suspense fallback={<div>Loading...</div>}><OtherComponent /> </Suspense>以上都是官網(wǎng)示例,在項目實際使用中,還沒有單獨對功能組件進行懶加載,可以依據(jù)業(yè)務(wù)租組件的復(fù)雜度決定是否使用懶加載,個人覺得路由的懶加載是有必要的。使用中我們用高階組件進行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中對路由進行定義,這里假設(shè)有三個路由地址:
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>);} }以上兩步,就完成了基本功能對實現(xiàn),我們來看下效果
使用Lazy使用lazy后會根據(jù)路由打包成多個chunk文件,進行按需加載。我們打印懶加載的組件信息,返回的是個對象,示意如下:
React.lazy(() =&gt; import(&#39;./components/Home/Home&#39;))返回對象主要屬性說明: $$typeof:對象類型,包括Symbol(react.lazy)、Symbol(react.element)、Symbol(react.portal)等等,在react源碼中有定義 _ctor:懶加載異步函數(shù),返回Promise對象,即 async () => import('./Home') _result:存儲懶加載異步函數(shù)執(zhí)行的結(jié)果,可能值為error、moduleObject.default(即? Home()) _status:當(dāng)前狀態(tài),初始值(-1)、Pending(0)、Resolved(1)、Rejected(2)查看react源碼,在react-dom.js文件下的beginWork函數(shù)中,可以看到LazyComponent的加載方式其實是調(diào)用了mountLazyComponent函數(shù),
switch (workInProgress.tag) {// ...case LazyComponent:{var _elementType = workInProgress.elementType;return mountLazyComponent(current$$1, workInProgress, _elementType, updateExpirationTime, renderExpirationTime);}// ... }查看mountLazyComponent函數(shù),最重要的地方是,下面會分步解析:
// 解析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函數(shù),其參數(shù)elementType為上面打印出的對象,返回懶加載的組件,下面列出了關(guān)鍵代碼,_thenable執(zhí)行ctor()異步函數(shù),拿到import的組件函數(shù)即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;}} }正常返回的lazyComponent._result隨后執(zhí)行resolveLazyComponentTag函數(shù),入?yún)閞eadLazyComponentType拿到的結(jié)果Component,由于我們的返回的是f home(),所以直接用shouldConstruct判斷Component的原型上是否有isReactComponent,如果存在則為class組件,否則為函數(shù)組件,代碼如下:
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; }之后執(zhí)行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; }執(zhí)行完上面的方法,懶加載的前期工作就差不多完成了,下面根據(jù)resolvedTag進行組件刷新,我們這里是ClassComponent,所以重點看這塊的更新方法updateClassComponent,下面我們逐段分析該方法
switch (resolvedTag) {// ...case ClassComponent:{child = updateClassComponent(null, workInProgress, Component, resolvedProps, renderExpirationTime);break;}// ...}updateClassComponent方法首先做了propTypes的校驗(如果在組件中設(shè)置了的話),注意無法在CreateElement中驗證lazy組件的屬性,只能在updateClassComponent中進行驗證。
{if (workInProgress.type !== workInProgress.elementType) {var innerPropTypes = Component.propTypes;if (innerPropTypes) {checkPropTypes(innerPropTypes, nextProps, // Resolved props'prop', getComponentName(Component), getCurrentFiberStackInDev);}}}然后檢查是否有context,如果有的話則設(shè)置Provider,并監(jiān)聽變化,隨后執(zhí)行實例化,最后執(zhí)行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標(biāo)志,決定是渲染fallback還是其子組件。
上面就是關(guān)于React.lazy的一些想要分享和記錄的一些內(nèi)容,如果存在錯誤的理解或更好的理解方式,希望多多交流
總結(jié)
以上是生活随笔為你收集整理的react.lazy 路由懒加载_React lazy/Suspense使用及源码解析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: oracle查询undo表空间使用率,检
- 下一篇: 对flex-grow和flex-shri
