最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to lazy load multiple components at a same time - Stack Overflow

programmeradmin3浏览0评论

I have 2 components, I want to load all of them using lazy load something like

const A = lazy(() => import("../test/A")); 
const B = lazy(() => import("../test/B")); 

This will create 2 separate bundles, and will import them when required.

But, I want to create a single bundle and when that bundle loads I should be able to use both above components.

I also don't want to create a single component containing both the above components as I want a separate route for both of them

I tried to do something like this =/src/App.js

Can please somebody will explain me Is this type of functionality possible, If yes then how and what am I doing wrong

I have 2 components, I want to load all of them using lazy load something like

const A = lazy(() => import("../test/A")); 
const B = lazy(() => import("../test/B")); 

This will create 2 separate bundles, and will import them when required.

But, I want to create a single bundle and when that bundle loads I should be able to use both above components.

I also don't want to create a single component containing both the above components as I want a separate route for both of them

I tried to do something like this https://codesandbox.io/s/eager-raman-mdqzc?file=/src/App.js

Can please somebody will explain me Is this type of functionality possible, If yes then how and what am I doing wrong

Share Improve this question asked Apr 27, 2020 at 17:06 AdarshAdarsh 5242 gold badges10 silver badges19 bronze badges 5
  • Have you tried Promise.all()? Wouldn't that do what you want? I'm not sure I understand exactly what you are trying to do. – zero298 Commented Apr 27, 2020 at 17:15
  • I'm curious as to why you want to manually control the code splitting - Is the automatic code splitting not working? Or is there another reason I didn't think of? – Garrett Motzner Commented Apr 27, 2020 at 21:20
  • Hello @GarrettMotzner, actually lazy loading 2 components will create 2 separate bundles but I want only 1 bundle to load multiple components i.e. I myself want to control what components will rely on what bundles, but the major issue is that I want seperate routes for them – Adarsh Commented Apr 28, 2020 at 8:18
  • Hello @zero298, I want to create a single bundle that comprises the only components that I want but with seperate routes – Adarsh Commented Apr 28, 2020 at 8:20
  • You can control when to show the component by using intersection observer. Zero config and less weight library(~3kb). npmjs.com/package/react-observer-api – Srigar Commented May 4, 2020 at 13:46
Add a comment  | 

5 Answers 5

Reset to default 10

There are probably some tuning options in the code splitter that might better accomplish what you are trying to achieve. But if you don't want to mess around with those (or they are not available to change because you are using a preset configuration), then perhaps you can combine the modules into a single file, and lazy load that "combo module".

To do that, we first need to know how lazy determines what component in a module to load, and what types of objects it expects. From the docs:

The React.lazy function lets you render a dynamic import as a regular component.

React.lazy takes a function that must call a dynamic import(). This must return a Promise which resolves to a module with a default export containing a React component.

The lazy component should then be rendered inside a Suspense component, which allows us to show some fallback content (such as a loading indicator) while we’re waiting for the lazy component to load.

You can place the Suspense component anywhere above the lazy component. You can even wrap multiple lazy components with a single Suspense component.

So according to that, if you want to use lazy() to wrap the module, then you have to have a component as the default property of the module. So it won't allow you to automatically use a module that uses named exports as a component. However, you can easily make a promise that transforms a named export to a default export, and wrap that in lazy:

// in comboModule.js:
export A from '../test/A'
export B from '../test/B'

// in the code that needs lazy modules
  const A = lazy(() => import('./comboModule').then((module) => ({default: module.A})))
  const B = lazy(() => import('./comboModule').then((module) => ({default: module.B})))

Note that we have to call import inside the initializer function passed to lazy, or the import will start immediately. Part of lazy's benefit is that is lets you wait until the parent component renders the lazy component before loading. However, import() should cache the result from the first import, and only load the code once.

In the initializer function, we use then to transform the result of import() from something like Promise({A: <Component>, B: <Component>}) to what lazy expects from the initializer function: Promise({default: <Component>})

Now we have two lazy components that both source from one module file.

Resources:

  • React code splitting
  • import/export
  • Promise.prototype.then (then returns a promise)

You can use Suspense for waiting both of them. There are two bundles, but you can wait of loading both of them

import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';

const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));

const App = () => (
  <Router>
    <Suspense fallback={<div>Loading...</div>}>
      <Switch>
        <Route exact path="/" component={Home}/>
        <Route path="/about" component={About}/>
      </Switch>
    </Suspense>
  </Router>
);

I wrote a utility function that generalizes what @garrett-motzner said in his answer:

import {lazy} from 'react';

export const multiLazy = moduleLoaderArray => {
  const promises = Promise.all(moduleLoaderArray.map(loader => loader()));
  return moduleLoaderArray.map((m, index) =>
    lazy(() => promises.then(results => results[index]))
  );
}

Works like a charm like this:

const [A, B] = multiLazy([
  () => import("../test/A"),
  () => import("../test/B"),
]);

So no need to create an intermediary component file (besides for the utility function of course)

I chose this syntax to stick as close as possible to the React.lazy one, but it could be modified to get multiLazy(['../test/A', '../test/B']) if preferred and often used.

You can use a proxy wrapper to the import statement to give React.lazy what it wants:

const bundle = new Proxy(import("../CombineModule"),{
  "get":function (esm,key){
    return esm.then(function (m){
      return {"__esMODULE":true,"default":m.default[key]};
    });
  }
});

var CompA = React.lazy(() => bundle.A)
var CompB = React.lazy(() => bundle.B)

Based on Garrett Motzner answer I made this helper for loading named components:

// in comboModule.js:
export default from '../test/A'
export B from '../test/B'

// in the code that needs lazy modules
const m = () => import('comboModule')
const ADefault = lazy(m);
const BNamed = lazy(m, m => m.B);


// the new lazy helper function:

export function lazy<T extends React.ComponentType<any>, Q extends { default: T }>(
    factory: () => Promise<Q>,
    selector?: (arg: Q) => () => JSX.Element
) {
    if (!selector) return React.lazy(factory);
    return React.lazy(() =>
        factory().then((module) => ({ default: selector(module) }))
    );
}

It does need a default export to exists or otherwise TypeScript will complain about that, but you can also load named components from the module.

发布评论

评论列表(0)

  1. 暂无评论