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>);}
}

以上两步,就完成了基本功能对实现,我们来看下效果

使用Lazy

使用lazy后会根据路由打包成多个chunk文件,进行按需加载。我们打印懒加载的组件信息,返回的是个对象,示意如下:

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;}}
}

正常返回的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的一些想要分享和记录的一些内容,如果存在错误的理解或更好的理解方式,希望多多交流

react.lazy 路由懒加载_React lazy/Suspense使用及源码解析相关推荐

  1. vue实现路由懒加载,react实现路由懒加载

    组件懒加载也叫按需加载: 当打包构建应用时,js 包会变得非常大,影响页面加载.如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了. 打包 bui ...

  2. react.lazy 路由懒加载_Vue面试题: 如何实现路由懒加载?

    非懒加载 import List from '@/components/list.vue' const router = new VueRouter({routes: [{ path: '/list' ...

  3. 免Root 实现App加载Xposed插件的工具Xpatch源码解析(一)

    前言 Xpatch是一款免Root实现App加载Xposed插件的工具,可以非常方便地实现App的逆向破解(再也不用改smali代码了),源码也已经上传到Github上,欢迎各位Fork and St ...

  4. React - 路由 lazyLoad 的使用(路由懒加载)

    React - 路由 lazyLoad(路由懒加载) lazy是React提供的懒(动态)加载组件的方法,React.lazy() 路由组件代码会被分开打包,能减少打包体积.延迟加载首屏不需要渲染的组 ...

  5. Vue-Router + Webpack 路由懒加载实现

    一.前言 https://segmentfault.com/a/1190000015904599 当打包构建应用时,Javascript 包会变得非常大,影响页面加载.如果我们能把不同路由对应的组件分 ...

  6. react中使用lazy函数进行路由懒加载

    react中使用lazy函数进行路由懒加载 import React, { Component,lazy,Suspense} from 'react' //1.通过React的lazy函数配合impo ...

  7. 14 代码分割之lazy:Suspense与路由懒加载

    lazy内置方法 Suspense内置组件 lazy是React提供的懒(动态)加载组件的方法,React.lazy() 能减少打包体积.延迟加载首屏不需要渲染的组件 依赖内置组件Suspense:给 ...

  8. react路由懒加载

    1.lazy,Suspense   fallback,路由懒加载 //引入Suspense从react中 import React,{Suspense} from 'react' import Hom ...

  9. umi路由懒加载和权限验证(基于React)

    一.路由懒加载 在 umirc.ts 配置文件中,添加以下配置即可: {dynamicImport: {loading: '@/components/Loading',}, } 其中,@/compon ...

最新文章

  1. OneAPM挂牌新三板,续写 ITOM 新篇章
  2. python中configparser详解_python ConfigParser模块详解
  3. P7408-[JOI 2021 Final]ダンジョン 3【贪心,树状数组】
  4. 大话数据结构——查找
  5. UVAoj 11324 - The Largest Clique(tarjan + dp)
  6. mysql hbase 同步_HBase 简介和使用 Sqoop 同步 Mysql 数据到 HBase
  7. 总结列表显示ListView知识点
  8. python爬取同花顺_python 同花顺
  9. PID控制算法的c语言实现 附录2 直流电机PWM调速系统中控制电压非线性研究
  10. 华为手机wifi不显示连接到服务器,华为手机中无法连接WIFI处理方法
  11. BZOJ.4340.[BJOI2015]隐身术(后缀数组 搜索)
  12. 苹果手机投影到墙上_实用派amp;小零碎:快充数据线、实木理线器、小电视支架、高清投影仪……...
  13. [ZJOI2015] 幻想乡战略游戏——树链剖分
  14. python笔试编程题_Python自动化测试笔试面试时常见的编程题
  15. Android获取百度音乐下载音乐和歌词下载链接
  16. 在贷款行业中,运营商大数据精准获客,是否真实有效呢
  17. 开始报名啦!——第二届融360“天机”金融风控大数据竞赛火热来袭
  18. online python compiler_在线控制台编译器:Online Console Compiler
  19. 七夕第一波狗粮来啦!魏晨晒婚纱照,与妻子爱情长跑十年成眷属
  20. PKCS#11及CSP接口标准

热门文章

  1. 分类树/装袋法/随机森林算法的R语言实现
  2. TP引用样式表和js文件及验证码
  3. Angular Material 攻略 04 Icon
  4. Lambda表达式的前世今生
  5. 查看oracle当前的连接数
  6. WPR-007:WPF中窗体的透明设置
  7. leetcode 403. 青蛙过河(dp)
  8. leetcode 406. 根据身高重建队列(贪心算法)
  9. github持续集成的设置_如何使用GitHub Actions和Puppeteer建立持续集成管道
  10. materialize_使用Materialize快速介绍材料设计