React 16.6 的新发布带来了一些只需很小努力就能给React组件对增长了不少力量的新特性。react
其中有两个是 React.Suspense
和 React.lazy()
, 这个很容易用在代码分割和懒加载上。webpack
这篇文章关注在如何在 React 应用中使用两个新特性和他们给 React 开发者带来的新的潜力。git
过去几年写 JavaScript 应用的方式进化了。在 ES6(modules)的出现,Babel 编译器,和其余打包工具像是 WebPack 和Browserify,JavaScript 应用如今能够用彻底现代化的模式写出容易维护的东西。es6
一般,每一个模块被导入合并在一个文件叫作 bundle,这些 bundle 在一张页面上包括了整个APP。然而,当 APP 增加的时候,这些 bundle 尺寸开始变得愈来愈大,所以影响了页面加载时间。github
打包工具像是 Webpack 和 Browserify 提供了代码分割的支持,能够在须要加载(懒加载)而不是一次性加载不一样的 bundles 中引入分割代码,从而提升 app 的表现。web
代码风格的主要方式之一是使用动态导入。动态导入做用于 import()
语法,这还不是 JavaScript 语言标准的一部分,可是一个指望不久被接受的提案。promise
调用 import()
去加载模块依赖 JavaScript 的 Promises。所以,返回一个完整的加载的模块或者若是模块不存在的话就拒绝。浏览器
这儿有一个用 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 组件的代码分割上。常见的实现是动态 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
实现什么。
在 React 16.6 中,支持基础组件的代码分割和懒加载已经经过 React.lazy()
和 React.Suspense
添加。
React.lazy() 和 Suspense 尚未支持服务端。服务端的代码分割,仍然使用 React-Loadable。
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>
}
复制代码
一个使用 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())
)
})
复制代码
下面的截屏展现了当渲染的时候组件看起来的示例。
若是你但愿使用一个命名的导出组件,那么你须要再次导出他们,做为在独立的中间模块中的默认导出。
若是你有一个 OtherComponent
做为命名导出模块,你但愿使用 React.lazy()
来加载 OtherComponent
,那么你须要建立一个中间模块来再次导出 OtherComponent
做为 默认模块。
export const FirstComponent = () => {/* 组件逻辑 */}
export const SecondComponent = () => {/* 组件逻辑 */}
export const OtherComponent = () => {/* 组件逻辑 */}
复制代码
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享受吧。