I Love ReactJS

How to do component-level demand loading in React

in React applications, as the number of components increases, so does the size of the application. This may cause the application to load for too long and bring a bad experience to the user. To solve this problem, we can use component-level demand loading. On-demand loading can improve application performance, reduce page loading time, and improve user experience. In this blog post, I will detail how to implement component-level on-demand loading in React.

what is demand loading?

on-demand loading (also known as lazy loading) is a technique that allows us to delay loading code until it really needs to be executed. In React applications, demand loading allows us to load components or other resources dynamically when needed, rather than when they are initially loaded. This means that applications can start faster, reduce unnecessary network requests and file sizes, and improve performance and response speed.

Why use demand loading?

the main reason for using demand loading in React applications is to improve performance and user experience. When we use demand loading, we only load code when needed, not all code when the application starts. This can reduce page loading time and network requests, and improve application response speed and user satisfaction. In addition, demand loading can reduce the file size of the application because we only need to load the necessary code and resources.

implementation of demand loading

React supports a variety of ways to load components on demand, including using React.lazy and Suspense API, using higher-level components, and using dynamic imports. Now I will introduce the usage of each method

use React.lazy and Suspense API

React.lazy is a function that allows us to load components dynamically. When a component is needed, React.lazy automatically loads the code and renders the component. Using React.lazy needs to be used with the Suspense component to handle the wait state during component loading.

use the React.lazy () function to create a lazy loading component:

const MyComponent = React.lazy(() => import('./MyComponent'));

where you need to use this component, wrap it in a component and set the fallback property to the placeholder in loading (for example, loading animation):

import React, { Suspense } from 'react';

function App() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <MyComponent />
      </Suspense>
    </div>
  );
}

it is important to note that React.lazy only supports default export components. If you want to dynamically load a named export component, you need to use dynamic import. For example, if we want to load the MyNamedComponent component dynamically, we can write:

import React, { lazy, Suspense } from 'react';

const MyNamedComponent = lazy(() => import('./MyNamedComponent').then(module => ({ default: module.MyNamedComponent })));

function App() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <MyNamedComponent />
      </Suspense>
    </div>
  );
}

export default App;

in the above code, we use the then method to dynamically get the MyNamedComponent component and then export it as the default component. This allows us to use React.lazy to dynamically load the MyNamedComponent component.

use react-loadable

react-loadable is a library of dynamically loaded components in React applications that allows us to load components when needed, rather than loading all components when the application is initialized. With react-loadable, we can easily implement on-demand loading at the component level.

install and use

to use react-loadable, we need to install it first. You can install it via npm or pnpm:

Npm install-save react-loadable
Or
Pnpm i react-loadable

use the react-loadable function to create a lazy loading component:

import Loadable from 'react-loadable';

const MyComponent = Loadable({
  loader: () => import('./MyComponent'),
  loading: () => <div>Loading...</div>,
});

use MyComponent directly where you need to use this component:

function App() {
  return <MyComponent />;
}

both of the above can achieve component-level on-demand loading and improve application performance and user experience. When executed in a project, both ways can be implemented. It is important to note, however, that when using packaging tools such as Webpack, you need to make sure that the dynamic import (dynamic import) syntax is used to achieve true on-demand loading.

actual combat

after reading the above theory and usage, we are almost ready to use it in the project. But when we are developing a component library, how do we write code when we expect that the way of demand loading is encapsulated in the component library? is there any special consideration that needs extra attention? Below I will use a simple demonstration demo to illustrate some possible problems and the corresponding solutions.

initialize a multi-package management project

find any multi-package management project, or you can create a few new front-end projects if you have mastered link debugging skills. Of course, for the consistency of the demonstration, I suggest you download the initial project I prepared for you directly.

git clone https://github.com/xiaohuoni/monorepo-demo.git
cd monorepo-demo/
pnpm i
pnpm build

create a new subpackage and verify it with an empty package of umi

# or manually create a new folder umi-demo
mkdir packages/umi-demo 
cd packages/umi-demo
npm init -y

install umi and antd

pnpm i umi antd

New packages/umi-demo/pages/index.tsx make the following modifications:

import { Button } from 'antd';

export default function HomePage() {
  return (
    <div>
      <Button>test</Button>
    </div>
  );
}

modify package.json

{
  "dev": "umi dev",
  "build": "umi build"
},

execute pnpm dev after installation is complete, and if all goes well, we should be able to see the test button on the page on a similar http://localhost:8000 port.

antd packages are loaded on demand in the project

the concept of "demand loading" here is a little similar but a little different from what we have always said about "demand load antd". By "demand load antd", we mean "demand load antd components" such as using only one button, and we expect the product to contain only the code for the button. Usually we use babel-plugin-import to deal with it. The final product may all be in a js file, and what we need to do here is to separate the antd package in a js file. (that is, the previous behavior of unpacking by default on demand loading)

execute build

pnpm build

Let's do a build first to see how the current project product

info  - File sizes after gzip:

  108.79 kB  dist/umi.js
  32.89 kB   dist/882.async.js
  201 B      dist/p__index.async.js

React.lazy load antd

modify the above code with the React.lazy function.

import React, { Suspense }  from 'React';

const Button = React.lazy(async () => {
  const antd = await import/* webpackChunkName: "antd" */('antd');
  return { default: antd.Button };
});

export default function HomePage() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <Button>test</Button>
      </Suspense>
    </div>
  );
}

perform compilation

pnpm build

... 

info  - File sizes after gzip:

  415.64 kB  dist/antd.async.js
  108.77 kB  dist/umi.js
  3.34 kB    dist/p__index.async.js

it is not difficult to find that a js file named "antd.async" is generated in the product. Although it is very large, because it contains all the antd components. The name of the file is determined by the magic code of webpack, that is, the comment code / * webpackChunkName: "antd" * / as we see.

of course, we can also import only the Button components we need, with simple changes to the above code.

import React, { Suspense }  from 'React';

const Button = React.lazy(async () => {
+  const antd = await import/* webpackChunkName: "antd" */('antd/lib/button');
+  return antd;
});

export default function HomePage() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <Button>test</Button>
      </Suspense>
    </div>
  );
}

this requires that the product of the corresponding component library contain separate files. Such as antd/lib. The current product file is cjs and esm can be used normally.

perform compilation

pnpm build

...

info  - File sizes after gzip:

  109.6 kB           dist/umi.js
  62.91 kB           dist/antd.async.js
  3.35 kB            dist/p__index.async.js

both of the above are fine, depending on whether our requirement scenario requires a full number of products.

effect demonstration
cd dist
npx serve

...
   ┌──────────────────────────────────────────────────┐
   │                                                  │
   │   Serving!                                       │
   │                                                  │
   │   - Local:            http://localhost:3000      │
   │   - On Your Network:  http://10.128.4.158:3000   │
   │                                                  │
   │   Copied local address to clipboard!             │
   │                                                  │
   └──────────────────────────────────────────────────┘

Open the browser developer tool and adjust the network to low-speed 3G. We will be able to see Loading...

before the js file is loaded.

because the antd is already 5.x at the time of the demonstration, if we are using a lower version of antd, we still have to deal with the loss of styles in the real scene.

use react-loadable

install react-loadable

pnpm i react-loadable

modify the above code with the React.lazy function.

import Loadable from 'react-loadable';

const Button = Loadable({
  loader: async () => {
    const antd = await import(/* webpackChunkName: "antd" */ 'antd/lib/button');
    return antd;
  },
  loading: () => <div>Loading...</div>,
});

export default function HomePage() {
  return (
    <div>
      <Button>test</Button>
    </div>
  );
}

perform compilation

pnpm build

...

info  - File sizes after gzip:

  109.49 kB       dist/umi.js
  62.91 kB        dist/antd.async.js
  2.26 kB  dist/p__index.async.js

you can see that the effects of the two are similar. It is worth noting that React.lazy (loader) and react-loadable ({loader}) these two loader are the same, and the difference lies in where the fallback is written before the load is completed.

so you still need to choose packages that you don't use according to the usage scenario, and you feel that react-loadable is more flexible in usage. On the other hand, React.lazy is more convenient, so you don't have to install another package. This is how it is used in the project.

antd packages are loaded on demand in the component library

We have a scenario where we write a component library for the project to use, expecting that the use of this component in the project is the same as the normal usage, but the effect needs to load js on demand. To put it simply, write the following code:

import { Button } from 'demo';

export default function HomePage() {
  return (
    <div>
      <Button>test</Button>
    </div>
  );
}

effect is equivalent to

import Loadable from 'react-loadable';

const Button = Loadable({
  loader: async () => {
    const button = await import(/* webpackChunkName: "demo" */ 'demo/lib/button');
    return button;
  },
  loading: () => <div>Loading...</div>,
});

export default function HomePage() {
  return (
    <div>
      <Button>test</Button>
    </div>
  );
}

although it is somewhat "bizarre" to say that the requirements are somewhat "bizarre", there are many real usage scenarios. If we encounter similar needs, we might as well give it a try.

install demo component library

modify packages/umi-demo/package.json dependencies

  "dependencies": {
    "antd": "^5.3.1",
    "react-loadable": "^5.5.0",
+   "demo":"workspace:*",
    "umi": "^4.0.61"
  },

because the demo package is a subpackage of our warehouse, our version number here uses pnpm supported workspace:* , or we can write the same version number as in the subpackage, so 0.0.1 .

then use the button component of demo as mentioned above.

perform compilation

pnpm build

...

info  - File sizes after gzip:

  111 kB              dist/umi.js
  2.26 kB             dist/p__index.async.js
  246 B               dist/demo.async.js

verify that it is valid, and then to implement on-demand loading in the component library, move the logic from our project to the component library.

modify the export file of packages/demo/src/index.tsx component library,

- export { default as Button } from './button';
+ import React from 'react';
+ import Loadable from 'react-loadable';

+ const Button = Loadable({
+   loader: async () => {
+     const button = await import(/* webpackChunkName: "demo" */ './button');
+     return button;
+   },
+   loading: () => <div>Loading...</div>,
+ });

+ export { Button };

you will see an error at import on line 6: Dynamic imports are only supported when the'--module' flag is set to 'es2020',' es2022', 'esnext',' commonjs', 'amd',' system', 'umd',' node16', or 'nodenext'.
Add "module": "ES2020" to the compilerOptions of tsconfig.json to repair

modify the usage in the project, packages/umi-demo/pages/index.tsx

import { Button } from 'demo';

export default function HomePage() {
  return (
    <div>
      <Button>test</Button>
    </div>
  );
}

perform compilation

pnpm build

...

info  - File sizes after gzip:

  110.98 kB (-21 B)  dist/umi.js
  4.95 kB (+328 B)   dist/p__index.async.js

will find that our function is invalid. At this point, you need to look at the main configuration in the component library package.json.

such as packages/demo/package.json now "main": "lib/index.js", you can see that main specifies cjs files, which have been compiled by babel once. The magic comments will also be removed. Fortunately, we are only loading js on demand at this time, but the project is running correctly and can be viewed at cd dist & & npx serve .

modify "main": "lib/index.js", to the esm directory, that is, "main": "es/index.js",

perform compilation

pnpm build

...

info  - File sizes after gzip:

  111 kB   dist/umi.js
  4.62 kB  dist/p__index.async.js
  171 B    dist/demo.async.js

Note: after modifying the main, you should confirm whether the corresponding files is in the package file, that is, "files": ["lib"] of package.json , and needs to add the corresponding directory

.

Summary: this function can be built into the component library, but the product of the component library is required to be esm.

if we want to compare the final result, we can cut the demo-for-loadable branch with the warehouse.

Summary

demand loading components is an important technique for optimizing the performance of React applications. This article introduces two ways to load components on demand: using React.lazy and Suspense API and using react-loadable components.

React.lazy and Suspense API are a simple and straightforward method officially provided by React that makes it easy to load components on demand.

react-loadable is a high-level component (Higher-Order Component) that encapsulates the component's loading process and provides many configuration options, such as delayed load time, error handling, and so on.

No matter which method we choose, loading components on demand is an important technique for optimizing the performance of React applications, and developers should make a wise choice between the requirements and advantages of the application.

Exit mobile version