章
目
录
随着前端项目不断迭代更新,代码量会持续增加,这使得项目打包后的静态资源体积越来越大。当用户打开网页时,加载时间也会随之变长,严重影响用户体验。尤其是当页面内容不加区分地全部加载,导致整个页面在loading阶段无法交互时,用户的感受会更差。
一、实际案例分析
以一个常见的页面为例,该页面由App组件构成,而App组件又包含Header、SideBar、Content和Footer这4个React组件,代码如下:
import React from 'react';
import Header from './Header';
import SideBar from './SideBar';
import Content from './Content';
import Footer from './Footer';
function App() {
return (
<div className="page">
<Header />
<SideBar />
<Content />
<Footer />
</div>
);
}
export default App;
在Fast 3G网络环境下,通过开发者工具Network可以看到,App组件的打包文件main.chunk.js大小为7.6kB,加载时间达到774ms。面对这种情况,我们思考一下,有没有办法在保证页面功能完整的前提下,尽可能缩短页面加载App组件的时间,提升初始化页面的可交互性呢?答案是通过延迟加载App组件中一些非关键的子组件,减小App组件的体积,从而实现快速打开页面的目的。
二、React.lazy与Suspense的使用方法
(一)React.lazy函数
在React中,我们通常使用ESM的import语法来引用组件,比如import OtherComponent from './OtherComponent';
。但如果想要实现延迟加载,就需要借助React.lazy函数。使用方式如下:
const OtherComponent = React.lazy(() => import('./OtherComponent'));
React.lazy的参数是一个回调函数,在这个回调函数里调用import
方法来加载组件。import
执行后会返回一个Promise,当这个Promise成功resolve时,就会返回真正的React组件,即OtherComponent
。通过这种方式,引入的组件只有在首次真正被渲染时才会加载,在此之前不会产生额外的请求,达到了延迟加载的效果。
(二)Suspense组件
Suspense组件是与React.lazy配合使用的。它的使用很简单,只需用它包裹通过React.lazy引入的组件即可。并且,Suspense组件提供了一个fallback
属性,我们可以设置fallback={<div>Loading...</div>}
,这样在等待加载lazy组件时,就会向用户展示“Loading…”的提示,告知用户内容正在加载中。例如:
import React, { Suspense } from'react';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
);
}
三、在实际项目中的应用
(一)单页面多组件场景
回到前面提到的包含Header、SideBar、Content和Footer组件的App页面。一般来说,用户进入页面时,首先关注到的是Header和SideBar组件,Content和Footer组件相对不是关键的首屏加载内容。所以,可以对Content和Footer组件进行延迟加载,修改后的代码如下:
import React, { Suspense } from'react';
import Header from './Header';
import SideBar from './SideBar';
// content和footer部分延迟加载
const Content = React.lazy(() => import('./Content'));
const Footer = React.lazy(() => import('./Footer'));
function App() {
return (
<div className="page">
<Header />
<SideBar />
<Suspense fallback={<div>Loading...</div>}>
<Content />
</Suspense>
<Suspense fallback={<div>Loading...</div>}>
<Footer />
</Suspense>
</div>
);
}
export default App;
延迟加载后,App组件打包文件(main.chunk.js)大小变为4.5kB,耗时664ms。相比之前,资源减少了3.1kB,体积缩减了41%,耗时减少了110ms,加载速度加快了14%。由此可见,在单页面多组件的场景下,延迟加载非关键组件,能有效减少首屏加载的资源体积,提升加载速度。对于大型复杂的业务应用,尤其是组件较多且展现优先级分明的页面,这种方式能显著提前用户可交互时间点,极大地提升用户体验。
(二)项目多路由场景
再来看一个路由相关的例子。假设有一个页面,包含3个菜单,对应3个路由入口:/home
对应Home组件,/business
对应Business组件,/manage
对应Management组件。最初的代码如下:
import React from "react";
import {
BrowserRouter as Router,
Switch,
Route,
Link
} from "react-router-dom";
import Home from './Home';
import Business from './Business';
import Management from './Management';
export default function BasicExample() {
return (
<Router>
<div>
<ul>
<li>
<Link to="/home">Home</Link>
</li>
<li>
<Link to="/business">Business</Link>
</li>
<li>
<Link to="/manage">Management</Link>
</li>
</ul>
<hr />
<Switch>
<Route exact path="/home">
<Home />
</Route>
<Route path="/business">
<Business />
</Route>
<Route path="/manage">
<Management />
</Route>
</Switch>
</div>
</Router>
);
}
在Fast 3G网络下,访问Home页面时,App组件打包文件(mian.chunk.js)大小为6.8kB,加载耗时785ms。加载完成后,菜单路由之间切换没有延迟。但考虑到用户进入系统后,通常默认先进入Home页面浏览操作,甚至可能只在Home页面停留,其他页面不会访问。为了加快Home页面的访问速度,同时不影响系统全局功能,可以对Business和Management组件进行延迟加载,仅在访问对应路由时才加载这两个组件,代码修改如下:
import React, { Suspense } from "react";
import {
BrowserRouter as Router,
Switch,
Route,
Link
} from "react-router-dom";
import Home from './Home';
// 延迟加载路由下的Business和Management组件
const Business = React.lazy(() => import('./Business'));
const Management = React.lazy(() => import('./Management'));
export default function BasicExample() {
return (
<Router>
<div>
<ul>
<li>
<Link to="/home">Home</Link>
</li>
<li>
<Link to="/business">Business</Link>
</li>
<li>
<Link to="/manage">Management</Link>
</li>
</ul>
<hr />
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/home">
<Home />
</Route>
<Route path="/business">
<Business />
</Route>
<Route path="/manage">
<Management />
</Route>
</Switch>
</Suspense>
</div>
</Router>
);
}
优化后,访问首屏Home页时,加载的main.chunk.js体积变为5.2kB,耗时703ms。与全部组件加载时相比,体积减少了1.6kB,缩减了23%,耗时减少82ms,缩短了10%。并且,当点击Business或Management菜单时,页面会出现Suspense设置的“loading…”提示,同时在Network中能看到新加载的对应chunk.js文件。由于Business和Management组件包裹在同一个Suspense内,它们共享相同的fallback
提示,这样既避免了为每个组件单独设置loading状态的繁琐,又便于对全局路由入口的loading状态进行统一管理。
在实际的系统中,往往存在多个路由入口,并且不同角色会访问不同页面。例如,角色A只能访问Home和Business页面,角色B只能访问Home和Management页面。对这些页面的组件进行延迟加载后,不仅能提高两个角色访问Home页面的速度,还能避免A加载其无需访问的Management页面资源,B也无需加载Business页面资源,这种优化对页面访问速度的提升效果明显,而且实现起来相对容易。
四、总结
在前端项目开发中,随着代码量的增加,打包文件体积膨胀,用户加载页面时间变长,还可能加载一些不必要的代码资源,这都影响了用户体验。此时,可以利用code – splitting技术来优化,在React中,React.lazy函数和Suspense组件的配合就能实现这一优化。具体实现方式是,通过React.lazy接收的函数中调用import
引入组件,然后用Suspense组件包裹引入的组件,并设置fallback
属性展示loading状态。这种延迟加载组件的方式,无论是在单个页面还是项目路由入口,都能避免无用资源的请求,提高页面首屏资源加载速度,从而有效提升用户体验。在实际开发中,对于单页面多组件的情况,要优先加载重要组件,延迟加载次要组件;对于项目多路由的情况,优先加载首屏路由组件,延迟加载其他路由组件。