使用Lazy + Suspense预加载React组件

13

我目前正在使用React 16与Suspense和Lazy来拆分我的代码库。虽然我想要预加载组件。

在我的以下示例中,我有两个路由。是否有办法在Prime挂载后立即预加载Demo? 我已经尝试在Prime页面的componentDidMount中创建另一个动态导入,但是React.lazy似乎没有意识到那是与下面的动态导入相同的文件。

import React, { lazy, Suspense } from 'react';
import { Switch, Route, withRouter } from 'react-router-dom';
import GlobalStyle from 'styles';

import Loading from 'common/Loading';
const Prime = lazy(() => import(/* webpackChunkName: "Prime" */'modules/Prime'));
const Demo = lazy(() => import(/* webpackChunkName: "Demo" */'modules/Demo'));

const App = () => (
  <main>
    <GlobalStyle />
    <Suspense fallback={<Loading>Loading...</Loading>}>
      <Switch>
        <Route path="/" component={Prime} exact />
        <Route path="/demo" component={Demo} />
      </Switch>
    </Suspense>
  </main>
);

export default withRouter(App);

因此,我尝试了不同的方法,例如使用和不使用 webpackChunkName,以及在componentDidMount中以不同的方式导入其他组件,如下所示。前两种引入文件的方法在componentDidMount中导致了Webpack错误,如下图底部所示。只有第三种方法得以继续,但是在图像中,文件 2.[hash].js 仅在访问页面后而不是在componentDidMount时加载。

进入图像描述

我错过了什么?

modules/Demo.jsx 的代码:

import React from 'react';

import LogoIcon from 'vectors/logo.svg';
import PageLink from 'common/PageLink';
import Anchor from 'common/Anchor';
import CenteredSection from 'common/CenteredSection';

const Demo = () => (
  <CenteredSection variant="green">
    <LogoIcon />
    <PageLink to="/" variant="green">Go to home page</PageLink>
  </CenteredSection>
);

export default Demo;

在Prime的componentDidMount中使用lazy对于预加载情况没有任何好处,因为它会在渲染时加载组件。2和3方法乍一看似乎不错,但是你在1和2中遇到的错误让我想要查看modules/Demo的代码,因为似乎有些东西导致了循环依赖。 - Ryan Cogswell
@RyanC 谢谢你的回复。我已经添加了演示页面的代码。你也可以在这里查看代码库:https://github.com/JBostelaar/react-prime/tree/progressive-web-app/src我也觉得第一种方法不会起作用,但我还是想展示一下我尝试过的东西。2会报错,3不会预加载页面。 - ronnyrr
代码结构与我预期的有些不同。我原本期望看到 modules/Demo.jsxmodules/Prime.jsx。我并不是说你现在的做法是“错误”的,但是使用 Demo/index.jsx 而不是我熟悉的方法会让我在给出更自信的指导时感到困惑。 - Ryan Cogswell
谢谢您的意见,但我相当确定问题不是来自我的文件夹结构或命名。 - ronnyrr
很好的webpackChunkName使用。我不知道你可以用这种方式命名你的代码块,谢谢;) - webmaster
3个回答

19

这非常容易做到,我认为对于lazy()和Suspense的内部工作原理存在误解。

React.lazy()唯一的期望是接受一个返回默认组件的Promise的函数。

React.lazy(() => Promise<{default: MyComponent}>)

所以如果你想要预加载,你只需要提前自己执行这个promise。

// So change this, which will NOT preload
import React from 'react';
const MyLazyComp = React.lazy(() => import('./path/to/component'));

/*********************************************/

// To this, which WILL preload
import React from 'react';

// kicks off immediately when the current file is imported
const componentPromise = import('./path/to/component');

// by the time this gets rendered, your component is probably already loaded
// Suspense still works exactly the same with this.
const MyLazyComp = React.lazy(() => componentPromise);

这个事实已经成为一种已知的签名,使得它对各种其他情况都非常有用。例如,我有一堆组件依赖于动态加载谷歌地图API,我能够创建一个函数来加载谷歌地图API,然后导入该组件。 我不会详细介绍此示例的内部原理,因为这是一个离题的内容,但重点是我创建了自己的一个函数,它执行了一些异步操作,然后返回一个 Promise 对象,其中包含 {default: Component}。

import React from 'react';
const MyLazyComp = React.lazy(() => importMapsComponent('./path/to/comp'));

但是通过这个更改,它只会在应用程序加载时简单地加载懒惰导入文件,那么这里的“懒惰”有什么用呢? - Pardeep Jain

1

我不确定这会有多大的帮助,但是这里有一个代码沙盒可以使用(Demo在componentDidMount中加载)。这是一个使用create-react-app进行配置的你的代码的简化版本。也许你可以以此作为起点,并逐渐将其变得更接近你的应用程序,以查看是什么导致动态导入不再按预期工作。


0
在我的情况下,这个问题进一步复杂化了,因为懒加载和需要启动预加载的组件之间有一个分离。
我发现你可以简单地使用以下代码行:
void (async () => import('the-lazy-import'))();

预加载导入,即使没有访问懒组件的权限。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接