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.mzph.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;用人能理解的表达方式去编写程序&#…