【译】懒加载组件

React 16.6 的新发布带来了一些只需很小努力就能给React组件对增长了不少力量的新特性。react

其中有两个是 React.SuspenseReact.lazy(), 这个很容易用在代码分割和懒加载上。webpack

这篇文章关注在如何在 React 应用中使用两个新特性和他们给 React 开发者带来的新的潜力。git

代码分割

过去几年写 JavaScript 应用的方式进化了。在 ES6(modules)的出现,Babel 编译器,和其余打包工具像是 WebPack 和Browserify,JavaScript 应用如今能够用彻底现代化的模式写出容易维护的东西。es6

一般,每一个模块被导入合并在一个文件叫作 bundle,这些 bundle 在一张页面上包括了整个APP。然而,当 APP 增加的时候,这些 bundle 尺寸开始变得愈来愈大,所以影响了页面加载时间。github

打包工具像是 Webpack 和 Browserify 提供了代码分割的支持,能够在须要加载(懒加载)而不是一次性加载不一样的 bundles 中引入分割代码,从而提升 app 的表现。web

Dynamic Imports

代码风格的主要方式之一是使用动态导入。动态导入做用于 import() 语法,这还不是 JavaScript 语言标准的一部分,可是一个指望不久被接受的提案。promise

调用 import() 去加载模块依赖 JavaScript 的 Promises。所以,返回一个完整的加载的模块或者若是模块不存在的话就拒绝。浏览器

对于老的浏览器,es6-promise 补充应该用来补充 Promise

这儿有一个用 Webpack 打包的app的内容,看起来是动态导入模块:babel

import(/* webpackChunkName: "moment" */ 'moment')
.then(({default: moment}) => {
  const tommorrow =moment().startOf('day').add(1, 'day');
  return tomorrow.format('LLL');
})
.catch(error => console.error("..."))
复制代码

当 Webpack 看到这样的语法,它会为 moment 库,动态建立一个分割包。app

对于 React 应用,若是使用 create-react-app 或者 Next.js,代码分割在 import()中悄悄产生。

然而,若是自定义了 Webpack的设置,你须要检查 Webpack 指导。对于 Babel 转化,你须要 yarnpkg.com/en/package/… 插件,容许 Babel 正确解析 import()

React 组件的代码分割

已经有几种技术应用于 React 组件的代码分割上。常见的实现是动态 import()在应用中懒加载路由组件——这个一般是做为基于路由代码分割的组件。

然而,这里有个叫 React-loadable 的很是流行的包用于 React 组件的代码分割。它提供一个高阶函数用 promise 来加载 React 组件,实现动态 import() 语法。

考虑下面叫作 MyComponent 的 React 组件:

import OtherComponent from './OtherComponent';

export defautl function MyComponent() {
  return (
    <div> <h1>My Component</h1> <OtherComponent /> </div>
  )
}
复制代码

这里,OtherComponent 是不会请求直到MyComponent开始渲染。然而,由于咱们静态导入了 OtherComponent,它会和 MyComponent 一块儿打包。

咱们可使用 react-loadable 去延迟加载 OtherComponent,直到咱们渲染MyComponent,从而代码分割成几个包。这里有个用 react-loadable 懒加载的OtherComponent

impoort Loadable from 'react-loadable';

const LoadableOtherComponent = loadable({
  loader: () => import('./OtherComponent'),
  loading: () => <div>Loading...</div>
});

export default function MyComponent() {
  return (
    <div>
      <h1>My Component</h1>
      <LoadableOtherComponent/>
    </div>
  )
}
复制代码

在这里能看到在选择对象中,组件被动态 import() 语法导入,赋值给 loader 属性。

React-loadable 也是用了 loading 属性去具体指出当等待真正组件加载时,将会渲染的回调组件。

你能够在这篇文档中了解你能经过 react-loadable 实现什么。

使用 Suspense 和 React.lazy()

在 React 16.6 中,支持基础组件的代码分割和懒加载已经经过 React.lazy()React.Suspense 添加。

React.lazy() 和 Suspense 尚未支持服务端。服务端的代码分割,仍然使用 React-Loadable。

React.lazy()

React.lazy() 很容易建立一个使用动态 import 的组件,并且像常规组件同样渲染。当组件被渲染时,它会自动打包包含这个加载的组件。

当调用 import() 加载组件,React.lazy()` 使用一个必须返回一个 promise 的参数的方法。这个默认导出包含 React 组件返回的 Promise 处理了模块。

当使用 React.lazy() 时,看起来像:

// 不使用 React.lazy()

import OtherComponent from './OtherComponent';

const MyComponent = () => {
  <div>
    <OtherComponent /> </div>
};

// 使用 React.lazy()

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

const MyComponent = () => {
  <div>
    <OtherComonment /> </div>
}
复制代码

Suspense

一个使用 React.lazy() 的组件只会在它须要的时候被加载。

所以,这里须要展现一些占位符内容的格式,当懒加载组件正在被加载的时候,好比用一个加载指示器。 这就是 React.Suspense 所建立的。

React.Suspense 是一个包裹了懒加载组件的组件。你能够在不一样的层级上使用一个 Suspense 组件包裹多个懒加载组件。

当全部懒加载组件加载后,这个 Suspense 组件使用 fallback 属性能够接受任何你想渲染的组件做为一个占位符。

import React, { Suspense } from 'react';

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

const MyComponent = () => {
  <div>
    <Suspense fallback={<div>Loading...</div>}>
      <LazyComponent />
    </Suspense>
  </div>
}
复制代码

若是组件懒加载失败,在懒加载之上放置明显的错误边界来展现不错的用户体验。

我在 CodeSandbox 上已经建立了一个很简单的例子来演示使用 React.lazy()Suspense 做为懒加载组件。

这里有个微型的app代码:

import React, { Suspense } from "react";
import Loader from "./components/Loader";
import Header from "./components/Header";
import ErrorBoundary from "./components/ErrorBoundary";

const Calendar = React.lazy(() => {
  return new Promise(resolve => setTimeout(resolve, 5 * 1000)).then(
    () =>
      Math.floor(Math.random() * 10) >= 4
        ? import("./components/Calendar")
        : Promise.reject(new Error())
  );
});

export default function CalendarComponent() {
  return (
    <div> <ErrorBoundary> <Header>Calendar</Header> <Suspense fallback={<Loader />}> <Calendar /> </Suspense> </ErrorBoundary> </div>
  );
}
复制代码

这里,一个很简单的 Loader 组件在懒加载 Calendar 组件中被建立用做回调内容。当懒组件 Calendar 加载失败,一个边界提示被建立来展现友好的错误。

我这里包裹了懒加载日从来模拟5秒延时。为了增长 Calendar 组件加载失败的几率,我也使用一个条件导入 Calendar 组件,或者返回一个promise的rejects。

const Calendar = React.lazy(() => {
  return new Promise(resolve => setTimeout(resolve, 5 * 1000)).then(
    () => Math.floor(Math.random() * 10 )>= 4 ?
     import("./components/Calendar"):
     Promise.reject(new Error())
  )
})
复制代码

下面的截屏展现了当渲染的时候组件看起来的示例。

test

命名导出

若是你但愿使用一个命名的导出组件,那么你须要再次导出他们,做为在独立的中间模块中的默认导出。

若是你有一个 OtherComponent 做为命名导出模块,你但愿使用 React.lazy() 来加载 OtherComponent,那么你须要建立一个中间模块来再次导出 OtherComponent 做为 默认模块。

Component.js

export const FirstComponent = () => {/* 组件逻辑 */}

export const SecondComponent = () => {/* 组件逻辑 */}

export const OtherComponent = () => {/* 组件逻辑 */}
复制代码

OtherComponent.js

export { OtherComponet as defatul } from './Components';
复制代码

这时候你可使用 React.lazy() 去加载 OtherComponent 从中间模块。

懒加载路由

使用 React.lazy()Suspense,如今很容易处理基于路由的代码分割而不使用其余外部依赖。你能够简单地转化应用的路由组建成为懒加载组件,包裹全部的路由经过 Suspense 组件。

下面的代码使用 React Router 展现了基于路由的代码分割:

import React, { Suspense } from 'react';
import { Router } from '@reach/router';
import Loading from './Loading';

const Home = React.lazy(() => import('./Home'));
const Dashboard = React.lazy(() => import('./Dashboard'));
const Overview = React.lazy(() => import('./Overview'));
const History = React.lazy(() => import('./History'));
const NotFound = React.lazy(() => import('./NotFound'));

function App() {
  return (
    <div>
      <Suspense fallback={<Loading />}>
        <Router>
          <Home path="/" />
          <Dashboard path="dashboard">
            <Overview path="/" />
            <History path="/history" />
          </Dashboard>
          <NotFound default />
        </Router>
      </Suspense>
    </div>
  )
}
复制代码

总结

With the new React.lazy()React.Suspense, code-splitting and lazy-loading React components has been made very easy.

如今开始从 React 16.6享受吧。

pic
相关文章
相关标签/搜索