• 代码分割
    • 打包
      • 示例
  • 代码分割
  • import()
  • React.lazy
    • Suspense
    • 异常捕获边界(Error boundaries)
  • 基于路由的代码分割
  • 命名导出(Named Exports)

    代码分割

    打包

    大多数 React 应用都会使用 Webpack 或 Browserify 这类的构建工具来打包文件。打包是一个将文件引入并合并到一个单独文件的过程,最终形成一个 “bundle”。接着在页面上引入该 bundle,整个应用即可一次性加载。

    示例

    App文件:

    1. // app.js
    2. import { add } from './math.js';
    3. console.log(add(16, 26)); // 42
    1. // math.js
    2. export function add(a, b) {
    3. return a + b;
    4. }

    打包后文件:

    1. function add(a, b) {
    2. return a + b;
    3. }
    4. console.log(add(16, 26)); // 42

    注意:

    最终你的打包文件看起来会和上面的例子区别很大。

    如果你正在使用 Create React App,Next.js,Gatsby,或者类似的工具,你会拥有一个可以直接使用的 Webpack 配置来进行打包工作。

    如果你没有使用这类工具,你就需要自己来进行配置。例如,查看 Webpack 文档上的安装和入门教程。

    代码分割

    打包是个非常棒的技术,但随着你的应用增长,你的代码包也将随之增长。尤其是在整合了体积巨大的第三方库的情况下。你需要关注你代码包中所包含的代码,以避免因体积过大而导致加载时间过长。

    为了避免搞出大体积的代码包,在前期就思考该问题并对代码包进行分割是个不错的选择。代码分割是由诸如 Webpack(代码分割)和 Browserify(factor-bundle)这类打包器支持的一项技术,能够创建多个包并在运行时动态加载。

    对你的应用进行代码分割能够帮助你“懒加载”当前用户所需要的内容,能够显著地提高你的应用性能。尽管并没有减少应用整体的代码体积,但你可以避免加载用户永远不需要的代码,并在初始加载的时候减少所需加载的代码量。

    import()

    在你的应用中引入代码分割的最佳方式是通过动态 import() 语法。

    使用之前:

    1. import { add } from './math';
    2. console.log(add(16, 26));

    使用之后:

    1. import("./math").then(math => {
    2. console.log(math.add(16, 26));
    3. });

    注意:

    动态 import() 语法目前只是一个 ECMAScript (JavaScript) 提案,而不是正式的语法标准。预计在不远的将来就会被正式接受。

    当 Webpack 解析到该语法时,它会自动地开始进行代码分割。如果你使用 Create React App,该功能已配置好,你能立刻使用这个特性。Next.js 也已支持该特性而无需再配置。

    如果你自己配置 Webpack,你可能要阅读下 Webpack 关于代码分割的指南。你的 Webpack 配置应该类似于此。

    当使用 Babel 时,你要确保 Babel 能够解析动态 import 语法而不是将其进行转换。对于这一要求你需要 babel-plugin-syntax-dynamic-import 插件。

    React.lazy

    注意:

    React.lazy 和 Suspense 技术还不支持服务端渲染。如果你想要在使用服务端渲染的应用中使用,我们推荐 Loadable Components 这个库。它有一个很棒的服务端渲染打包指南。

    React.lazy 函数能让你像渲染常规组件一样处理动态引入(的组件)。

    使用之前:

    1. import OtherComponent from './OtherComponent';
    2. function MyComponent() {
    3. return (
    4. <div>
    5. <OtherComponent />
    6. </div>
    7. );
    8. }

    使用之后:

    1. const OtherComponent = React.lazy(() => import('./OtherComponent'));
    2. function MyComponent() {
    3. return (
    4. <div>
    5. <OtherComponent />
    6. </div>
    7. );
    8. }

    这个代码将会在渲染组件时,自动导入包含 OtherComponent 组件的包。

    React.lazy 接受一个函数,这个函数需要动态调用 import()。它必须返回一个 Promise,该 Promise 需要 resolve 一个 defalut export 的 React 组件。

    Suspense

    如果在 MyComponent 渲染完成后,包含 OtherComponent 的模块还没有被加载完成,我们可以使用加载指示器为此组件做优雅降级。这里我们使用 Suspense 组件来解决。

    1. const OtherComponent = React.lazy(() => import('./OtherComponent'));
    2. function MyComponent() {
    3. return (
    4. <div>
    5. <Suspense fallback={<div>Loading...</div>}>
    6. <OtherComponent />
    7. </Suspense>
    8. </div>
    9. );
    10. }

    fallback 属性接受任何在组件加载过程中你想展示的 React 元素。你可以将 Suspense 组件置于懒加载组件之上的任何位置。你甚至可以用一个 Suspense 组件包裹多个懒加载组件。

    1. const OtherComponent = React.lazy(() => import('./OtherComponent'));
    2. const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
    3. function MyComponent() {
    4. return (
    5. <div>
    6. <Suspense fallback={<div>Loading...</div>}>
    7. <section>
    8. <OtherComponent />
    9. <AnotherComponent />
    10. </section>
    11. </Suspense>
    12. </div>
    13. );
    14. }

    异常捕获边界(Error boundaries)

    如果模块加载失败(如网络问题),它会触发一个错误。你可以通过异常捕获边界(Error boundaries)技术来处理这些情况,以显示良好的用户体验并管理恢复事宜。

    1. import MyErrorBoundary from './MyErrorBoundary';
    2. const OtherComponent = React.lazy(() => import('./OtherComponent'));
    3. const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
    4. const MyComponent = () => (
    5. <div>
    6. <MyErrorBoundary>
    7. <Suspense fallback={<div>Loading...</div>}>
    8. <section>
    9. <OtherComponent />
    10. <AnotherComponent />
    11. </section>
    12. </Suspense>
    13. </MyErrorBoundary>
    14. </div>
    15. );

    基于路由的代码分割

    决定在哪引入代码分割需要一些技巧。你需要确保选择的位置能够均匀地分割代码包而不会影响用户体验。

    一个不错的选择是从路由开始。大多数网络用户习惯于页面之间能有个加载切换过程。你也可以选择重新渲染整个页面,这样您的用户就不必在渲染的同时再和页面上的其他元素进行交互。

    这里是一个例子,展示如何在你的应用中使用 React.lazy 和 React Router 这类的第三方库,来配置基于路由的代码分割。

    1. import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
    2. import React, { Suspense, lazy } from 'react';
    3. const Home = lazy(() => import('./routes/Home'));
    4. const About = lazy(() => import('./routes/About'));
    5. const App = () => (
    6. <Router>
    7. <Suspense fallback={<div>Loading...</div>}>
    8. <Switch>
    9. <Route exact path="/" component={Home}/>
    10. <Route path="/about" component={About}/>
    11. </Switch>
    12. </Suspense>
    13. </Router>
    14. );

    命名导出(Named Exports)

    React.lazy 目前只支持默认导出(default exports)。如果你想被引入的模块使用命名导出(named exports),你可以创建一个中间模块,来重新导出为默认模块。这能保证 tree shaking 不会出错,并且不必引入不需要的组件。

    1. // ManyComponents.js
    2. export const MyComponent = /* ... */;
    3. export const MyUnusedComponent = /* ... */;
    1. // MyComponent.js
    2. export { MyComponent as default } from "./ManyComponents.js";
    1. // MyApp.js
    2. import React, { lazy } from 'react';
    3. const MyComponent = lazy(() => import("./MyComponent.js"));