react路由组件动态加载-优化首屏加载速度
通常情况下,使用
create-react-app
进行打包后,会生成最终打包文件main.js
,且这个文件在项目内容变多,引用第三方插件后,但得很大(>100kb)。
- 通过
Code Spliting
进行代码拆分,并使用动态import
使路由对应组件在使用时才被加载,可以优化打包,由原来一个main.js生成为多个单独的js
文件,并在首次加载时,只加载首屏用到的组件,从而提高首屏加载速度。 - 官方提供了
React.lazy
来进行组件的动态导入,用以优化页面加载速度
动态import与React Router V4
- 【旧方式】通常我们在写React路由时,是以以下这种方式:
/* Import the components */
import Home from "./containers/Home";
import Posts from "./containers/Posts";
import NotFound from "./containers/NotFound";
/* Use components to define routes */
export default () =>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/posts/:id" exact component={Posts} />
<Route component={NotFound} />
</Switch>;
-
Switch
用于渲染匹配当前路径的路由 - 以上,当我们将所有组件都在文件顶部引入时,意味着所有的组件都会被全部加载,而我们通过
Code Spliting
可以实现只加载匹配当前路径的路由对应组件功能。
优化CodeSpliting
构建一个异步导入组件 Async Component
首先我们需要创建一个用于异步加载组件的函数:
- 添加如下文件
src/components/AsyncComponent.js
import React, { Component } from "react";
export default function asyncComponent(importComponent) {
class AsyncComponent extends Component {
constructor(props) {
super(props);
this.state = {
component: null
};
}
async componentDidMount() {
const { default: component } = await importComponent();
this.setState({
component: component
});
}
render() {
const C = this.state.component;
return C ? <C {...this.props} /> : null;
}
}
return AsyncComponent;
}
- 关于
async-await
的理解可以参考这篇文章:异步神器async-await
以上代码做了如下几件事:
-
asyncComponent
函数以一个importComponent
函数作为参数,importComponent
用于动态调用一个组件。 - 在生命周期函数
componentDidMount
中,我们通过调用importComponent
来引入函数,并存入到asyncComponent
组件的state
中 - 最后,如果组件已完成加载,我们有条件地呈现该组件。在
render
中,我们除了可以简单使用return null
来应对未加载组件情况外,还可以使用一个loading spinner
加载动画组件来提升用户体验,避免白屏。
使用异步组件
原来引用组件的方式如下:
import Home from './containers/Home'
- 【优化写法】
const AsyncHome = asyncComponent(()=>import('./containers/Home'))
- 注意:这里我们只是在AysncHome组件被创建时使用了一个函数来动态
import()
,并没有直接同步的导入了Home组件。webpack
将会基于此做代码拆分。
// 最后使用异步组件
<Route path="/" exact component={AsyncHome} />
完整示例
src/Routes.js
import React from "react";
import { Route, Switch } from "react-router-dom";
import asyncComponent from "./components/AsyncComponent";
import AppliedRoute from "./components/AppliedRoute";
import AuthenticatedRoute from "./components/AuthenticatedRoute";
import UnauthenticatedRoute from "./components/UnauthenticatedRoute";
const AsyncHome = asyncComponent(() => import("./containers/Home"));
const AsyncLogin = asyncComponent(() => import("./containers/Login"));
const AsyncNotes = asyncComponent(() => import("./containers/Notes"));
const AsyncSignup = asyncComponent(() => import("./containers/Signup"));
const AsyncNewNote = asyncComponent(() => import("./containers/NewNote"));
const AsyncNotFound = asyncComponent(() => import("./containers/NotFound"));
export default ({ childProps }) =>
<Switch>
<AppliedRoute
path="/"
exact
component={AsyncHome}
props={childProps}
/>
<UnauthenticatedRoute
path="/login"
exact
component={AsyncLogin}
props={childProps}
/>
<UnauthenticatedRoute
path="/signup"
exact
component={AsyncSignup}
props={childProps}
/>
<AuthenticatedRoute
path="/notes/new"
exact
component={AsyncNewNote}
props={childProps}
/>
<AuthenticatedRoute
path="/notes/:id"
exact
component={AsyncNotes}
props={childProps}
/>
{/* Finally, catch all unmatched routes */}
<Route component={AsyncNotFound} />
</Switch>
;
- 通过以上优化后,我们可以再次打包看看
npm run build
- 打包后可以看到:
- 以上任何
.chunk.js
文件都对应着一个动态import()
导入的组件,当项目体量变量时,这样的优化将会更明显。
实际应用
- 以上动态加载组件的函数
asyncComponent
实际已经有对应的成熟的库React.lazy
。下面示例介绍如何使用React Router
和React.lazy
设置基于路由的代码拆分应用。
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import React, { Suspense, lazy } from 'react';
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>
);
- Suspense组件用于处理组件未加载完成时,显示loading的情况。
fallback
接受任意React组件
写在最后
- 通过
动态加载
可以很好的拆分代码,提升加载速度,而实现路由动态加载的关键在于:一个动态导入组件的函数
、对路由组件使用动态导入
给组件加载过程中加loading spinner
,当组件加载耗时长或者失败时,需要一个友好提示。
- 推荐使用
react-loadable
,简单使用方式如下:
$ npm install --save react-loadable
// 在异步组件中使用
const AsyncHome = Loadable({
loader: () => import("./containers/Home"),
loading: MyLoadingComponent
});
// MyLoadingComponent长这样:
const MyLoadingComponent = ({isLoading, error}) => {
// Handle the loading state
if (isLoading) {
return <div>Loading...</div>;
}
// Handle the error state
else if (error) {
return <div>Sorry, there was a problem loading the page.</div>;
}
else {
return null;
}
};