在React应用中,有些组件可能不经常用到,比如法律条款的弹窗,我们几乎不看,这些组件也就没有必要首次加载,可以在点击它们的时候再加载,这就需要动态引入组件,需要组件的时候,才引入组件,加载它们,进行渲染,也称为懒加载。怎么动态引入组件呢?先看在普通的JS中,如何动态引入一个模块?是使用import()函数。比如,在下面的index.html中,

<body>
<div id="message"></div>
<button id="clickShow">点击显示</button>
<script src="./index.js"></script>
</body>

  点击button,id为message的div会显示‘Hello’,假设显示内容的JS文件是showMessage.js

export function sayHi(id, msg) {
document.getElementById(id).innerHTML = msg;
}

  那么在index.js中,button的click事件处理函数中,import('./showMessage.js'),

function handleClick() {
import("./showMessage.js")
.then((module) => {
module.sayHi("message","Hello");
});
}
document
.getElementById("clickShow")
.addEventListener("click", handleClick);

  import()函数返回的是promise, promise resolve后返回的是module对象(showMessage.js中暴露出来的对象),通过module对象就可以调用showMessage中暴露的方法。在React中,也是通过添加点击事件来动态引入组件吗?有更好的方法,因为React是数据驱动,状态的改变,渲染不同的组件,就可以了。比如,弹窗,在包含弹窗组件的父组件中,设置state来控制弹窗的显示和隐藏,state是true,弹窗显示,state为false, 弹窗隐藏,但组件怎么表示出它是动态引入的组件?React.lazy()方法,它接受一个函数,返回一个组件,表示组件是动态加载的。函数的格式是() => import(要引入组件所在的js文件),js文件必须用export default 暴露出组件。假设Model.js中 export default function Module() {},

const LazyModel = React.Lazy(() => import('./Model.js');

  LazyModel组件就表示它要动态加载。当state是true时,显示LazyModel就可以了。但这会引出另外一个问题,state为true,React就会渲染LazyModel组件,但组件并没有加载完成,因为它是动态加载的,React是无法渲染的,但React肯定要渲染点什么,怎么办?怎么才能把动态加载的组件和React的渲染过程结合起来?需要告诉React,当组件没有加载完成时,无法渲染的时候,它要渲染什么,这就是suspense组件。用suspense组件把动态渲染的组件包起来,同时给suspense组件的fallback属性赋值,当React渲染动态组件的时候,它就会到组件的上层找Suspense组件,把fallback渲染出来。

  npx create-react-app react-lazy,   创建如下项目

  点击button的时候,动态显示日历。日历组件使用react-datepicker,npm install react-datepicker --save。App.js

import './App.css';

import { CalendarWrapper } from './CalenderWrapper'

function App() {
return (
<div>
<main>Main App</main>
<aside>
<CalendarWrapper />
<CalendarWrapper />
</aside>
</div>
);
} export default App;

  App.css

main {
width: 400px;
height: 60px;
background-color: #643797;
text-align: center;
line-height: 60px;
} aside {
display: flex;
justify-content: space-between;
width: 400px;
margin-top: 10px;
} .CalendarWrapper {
display: flex;
justify-content: center;
align-items: center;
width: 180px;
height: 100px;
background-color: #8aceea;
}

  CalendarWrapper.js

import { lazy, useState } from 'react';
const LazyCalendar = lazy(() => import("./Calendar.js")); export function CalendarWrapper() {
const [isOn, setIsOn] = useState(false); return <div className='CalendarWrapper'>
{
isOn ? (
<LazyCalendar />
) : (
<div>
<button onClick={() => setIsOn(true)}>Show Calendar</button>
</div>
)
}
</div>
}

  Calendar.js

import DatePicker from "react-datepicker";
 import "react-datepicker/dist/react-datepicker.css";
export default function Calendar() {
const startDate = new Date();
return (
<DatePicker
selected={startDate}
/>
);
}

  const LazyCalendar = lazy(() => import("./Calendar.js")); 可以看到React.lazy函数接受一个函数,返回一个promise,拆分一下

const getPromise = () => import('./Calendar.js');
const LazyComponent = lazy(getPromise);

  传给lazy函数getPromise()函数,React第一次渲染组件的时候,就会调用这个函数。getPromise()函数,返回一个promise,promise resolve后是一个模块,模块就是Calendar.js export default出来的模块。但是单击button后,有报错,因为React需要时间去加载组件,当渲染组件时,组件又没有加载完时,我们应该怎么做,最好显示一个loading提示,表示组件正在加载中。怎么样显示loading提示呢?使用Suspense组件,把Lazy组件包起来,

<aside>
<Suspense fallback={<div>Loading...</div>}>
<CalendarWrapper />
</Suspense>
<Suspense fallback={<div>Loading...</div>}>
<CalendarWrapper />
</Suspense>
</aside>

  使用fallback属性来表示Suspense组件将要渲染什么,直到所有后代Lazy组件都加载完成,返回UI。点击左边Show Calendar按钮,可以看到先渲染loading,再显示日历组件。当点击右边的Show Calendar按钮,你会发现,直接就渲染出了。异步加载的组件如果已经加载了,它就不需要再加载了。所以这两个异步加载的组件也可以放到一个<Suspense> 下面

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

  当一个lazy组件第一次渲染时,React会沿着组件树向上查找Suspense组件,然后使用第一个找到的Suspense组件。Suspense组件就会在它子组件的地方渲染它的fallback UI,如果没有找到Suspense组件,React就会报错。

  但React是怎么实现组件的动态加载的?我们可以认为动态加载的组件有一个内部状态,uninitialized, pending, resolved, or rejected。当React第一次渲染动态加载的组件时,组件是uninitialized状态,但它返回了一个函数,React会调用函数,来加载组件。函数就像getPromise

const getPromise = () => import("./Calendar");
const LazyCalendar = lazy(getPromise);

  函数返回了一个promise,组件的状态就变成了pending 状态,等待promise完成。promise应该能resolve成一个模块,模块的default属性是一个component。一旦promise被resolve,React就会设置组件的状态是resolved,然后返回组件,表示可以渲染了。

if (status === "resolved") {
  return component;
} else {
  throw promise;
}

  else则是和suspense组件进行沟通的关键,如果promise 没有resolve呢?它就会抛出promise, suspense组件就是捕获promise,然后渲染fallback UI(如果 promsise的状态是pending)。 所以对一个动态渲染的组件来说,如果它已经包含了一个组件,就是组件已经resolve了,React就会直接渲染组件;如果它包含一个正在pending的promise,组件就会抛出promise,Suspense组件就会捕获它。如果它包含一个函数,它就会调用函数来获取promise,把promise存在组件对象上,抛出promise,等到promise resolve后,调用promise的then方法 把promise resolve的组件存储在组件对象上。

  如果promsise 被rejected,比如网络错误,suspense component 不会处理error,需要error boundary。react并没有提供一个组件来捕获子组件抛出来的错误,但是它提供了一系列的生命周期函数,如果在类组件中想要捕获错误,就要实现这些生命周期函数,如果一个类实现一个或几个这样的生命周期函数,它们就称为错误边界。如果你把组件包到错误边界中,如果被包裹的组件中抛出错误,它就是渲染fallback  UI。

<ErrorBoundary>
  <Suspense fallback={<div>Loading...</div>}>
    <CalendarWrapper />
    <CalendarWrapper />  
  </Suspense>
</ErrorBoundary>

  Error buond 组件

import { Component } from "react";
export default class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError() {
return { hasError: true };
}
render() {
const {
children,
fallback = <h1>Something went wrong.</h1>
} = this.props;
return this.state.hasError ? fallback : children;
}
}

  当React去渲染动态加载的组件时,它先判断组件的状态,如果动态引入的组件已经加载完了,直接渲染组件。如果组件还在pending状态,React就会抛出动态引入的promise,如查promise rejected,需要一个error bound 来捕获异常,并渲染fallback UI。

React Lazy 和 Suspense的更多相关文章

  1. React.lazy和React.Suspense异步加载组件

    在React16.6中引入了React.lazy和React.Suspense,这两个组件,可以用来实现异步加载组件. 例如: const johanComponent = React.lazy(() ...

  2. React中异步模块api React.lazy和React.Suspense

    React.lazy React.lazy 这个函数需要动态调用 import().它必须返回一个 Promise,该 Promise 需要 resolve 一个 defalut export 的 R ...

  3. React源码 Suspense 和 ReactLazy

    React 16.6 提供的一个新的开放一部分功能的 Suspense 代码 import React, { Suspense, lazy } from 'react' const LazyComp ...

  4. 深入理解React:懒加载(lazy)实现原理

    目录 代码分割 React的懒加载 import() 原理 React.lazy 原理 Suspense 原理 参考 1.代码分割 (1)为什么要进行代码分割? 现在前端项目基本都采用打包技术,比如 ...

  5. 成为高级 React 开发你需要知道的知识点

    简评:除了常见的 HOC 和 RenderProp 技巧,作者介绍了 7 个有用的知识点. 使用 Fragment 而不是 div 很多时候我们想要处理多个 component,但是 render 只 ...

  6. 如何实现 React 模块动态导入

    如何实现 React 模块动态导入 React 模块动态导入 代码分割 webpack & code splitting https://reactjs.org/docs/code-split ...

  7. React性能优化总结

    本文主要对在React应用中可以采用的一些性能优化方式做一下总结整理 前言 目的 目前在工作中,大量的项目都是使用react来进行开展的,了解掌握下react的性能优化对项目的体验和可维护性都有很大的 ...

  8. react 项目的性能优化

    react 项目的性能优化有哪些? 1.react 模块化处理 就是将react内容进行模块化划分,一个模块一个组件,react更新机制是组件重新更新 2.在react项目中更新数据,不要直接将 us ...

  9. 2023 年最新最全的 React 面试题

    React 作为前端使用最多的框架,必然是面试的重点.我们接下来主要从 React 的使用方式.源码层面和周边生态(如 redux, react-router 等)等几个方便来进行总结. 1. 使用方 ...

  10. 【后台管理系统】—— Ant Design Pro入门学习&项目实践笔记(三)

    前言:前一篇记录了[后台管理系统]目前进展开发中遇到的一些应用点,这一篇会梳理一些自己学习Ant Design Pro源码的功能点.附:Ant Design Pro 在线预览地址. Dashboard ...

随机推荐

  1. 显示器AVG、DVI、HDMI、DisplayPort、Type-C、雷电接口

    在近十年的发展,显示设备的接口发生了巨大的改变.以前使用比较多的是蓝色VGA接口,接著出现了白色的DVI接口,当遇到不同接口时,还得买转接头进行转接.后来,又有了HDMI等接口,现在则出现DP和USB ...

  2. linux文本三剑客之awk详解

    linux文本三剑客之awk详解 目录 linux文本三剑客之awk详解 1.awk命令详解 1.1 awk的处理流程 1.2 awk中的变量 1.2.1 内置变量 1.2.2 自定义变量 1.3 a ...

  3. 仅1.0MB,开源压缩软件7-Zip

    首发微信公众号:SQL数据库运维 原文链接:https://mp.weixin.qq.com/s?__biz=MzI1NTQyNzg3MQ==&mid=2247485212&idx=1 ...

  4. C语言:汉诺塔问题(Hanoi Tower)------递归算法

    汉诺塔问题是一个经典的问题.汉诺塔(Hanoi Tower),又称河内塔,源于印度一个古老传说.大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘.大梵天命令婆 ...

  5. ollama 源代码中值得阅读的部分

    阅读 Ollama 源代码以了解其内部工作机制.扩展功能或参与贡献,以下是一些值得重点关注的部分: 1. 核心服务模块: 查找负责启动和管理模型服务的主程序或类,这通常是整个项目的核心逻辑所在.关注如 ...

  6. 排查Python卡慢神器

    如果遇到Python正在运行中的进程卡住,找不到原因.可以试试以下工具方法, 对于python就像jstack对于java一样. 法一 使用pystack-debugger 安装方式如下: yum i ...

  7. 从xib初始化的UIView如何继承?

    一.如何从xib自定义一个CustomView 1)首先创建继承自UIView的子类CustomView 2)创建名字为CustomView的View的Interface文件 3)在xib的资源文件中 ...

  8. ReplayKit2 有线投屏项目总结

    一.实现目标 iOS11.0以上设备通过USB线连接电脑,在电脑端实时看到手机屏幕内容 画质达到超清720级别,码率可达到1Mbps以上 二.实现技术方案设计 1.手机端采用ReplayKit2框架, ...

  9. vue 实现商品列表的添加、删除,搜索

    大江东去,浪淘尽,千古风流人物.故垒西边,人道是,三国周郎赤壁.乱石穿空,惊涛拍岸,卷起千堆雪.江山如画,一时多少豪杰.遥想公瑾当年,小乔初嫁了,雄姿英发.羽扇纶巾,谈笑间,樯橹灰飞烟灭.故国神游,多 ...

  10. 震惊!docker镜像还有这些知识,你都知道吗?----镜像(一)

    镜像操作命令表格 docker image 子命令 docker子命令 功能 docker image build docker build 从Dockerfile开始构建镜像 docker imag ...