前言
之前咱们聊了普通网页的图片懒加载 —— 让图片 “藏” 在屏幕外摸鱼,滚到眼前再开工。那 React 项目里的组件和图片呢?总不能一打开页面就把所有组件的代码都拽过来吧?
想象下:你打开购物App页面,首页就加载了 “订单页”“个人中心” 的代码,结果用户根本没点进去 —— 这不是纯纯的浪费流量和性能吗?React 的懒加载,就是给组件和图片发 “摸鱼许可证”:没出现在视野里,就别加载,躺平到要用时再干活。
一、React 里的 “组件懒加载”:React.lazy + 动态导入
我们先创建一个Demo组件,并看这行代码:
// 别直接import Demo,改成动态导入
const Demo = lazy(() => import('./Demo'));
这行代码的魔力在于:
搭配Suspense(不然组件加载时会 “崩”)
不过光用lazy还不够 —— 组件加载需要时间,加载过程中页面会报错。得用Suspense当 “占位符”:
import { lazy, Suspense } from 'react';
const Demo = lazy(() => import('./Demo'));
export default function App() {
return (
<div>
{/* 一堆内容... */}
<Suspense fallback={<div>组件加载中...</div>}>
<Demo />
</Suspense>
</div>
);
}
fallback里写的就是组件加载时的 “Loading 占位”,比如转圈圈、提示文字,用户体验会更友好。
二、React 里的 “图片 / 内容懒加载”:react-lazyload 或 自定义组件
组件懒加载管的是 “代码包”,图片 / 内容懒加载管的是 “可视区域内才渲染内容”。我常用的有两种方案:
方案 1:直接用react-lazyload库(现成的轮子)
仓库地址: www.npmjs.com/package/rea…
下载方法:npm i react-lazyload
导入的LazyLoad就是这个库,直接包在图片外面:
import LazyLoad from 'react-lazyload';
// 在App.jsx里用:
<LazyLoad placeholder={<div>图片加载中...</div>} offset={300}>
<img src="https://xxx.png" alt="" />
</LazyLoad>
完整代码(为了更好展现效果,我放了50个p标签):
代码高亮:
import LazyLoad from 'react-lazyload';
import { lazy } from 'react'
// import Demo from './Demo'
import MyLazyLoad from './MyLazyLoad'
// 组件没有出现在可视区域时,组件代码都不会被加载,被import('./Demo')包裹的模块会单独打包
const Demo = lazy(() => import('./Demo'));
export default function App() {
return (
<div>
{/* <Demo></Demo> */}
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<p>xxx</p>
<MyLazyLoad placeholder={<div>loading...</div>} width='100px' onContentVisible={() => console.log('onContentVisible')} onClose={() => console.log('onClose')}>
{/* <img src="https://inews.gtimg.com/om_bt/OG4Cnt2SgXAuTj-Vv77ASGszUj1BwOhUXtBCplSlBfQmAAA/641" alt="" /> */}
<Demo></Demo>
</MyLazyLoad>
<LazyLoad placeholder={<div>loading...</div>} offset={300}>
<img src="https://inews.gtimg.com/om_bt/Os3eJ8u3SgB3Kd-zrRRhgfR5hUvdwcVPKUTNO6O7sZfUwAA/641" alt="" />
</LazyLoad>
</div>
)
}
可以看到最开始的界面显示的都是loading:

我们慢慢往下滑:

滑到Demo和 照片:

方案 2:自己写一个懒加载组件(比如我自己写的MyLazyLoad.jsx)
我写的MyLazyLoad组件,核心就是用IntersectionObserver实现 “可视区域检测”,逻辑和之前普通网页的懒加载是通的:
// MyLazyLoad.jsx核心代码
import { useState, useRef, useEffect } from 'react';
export default function MyLazyLoad(props) {
const { placeholder, children, offset, onContentVisible } = props;
const [visible, setVisible] = useState(false);
const containerRef = useRef(null);
const elementObserver = useRef();
// 初始化IntersectionObserver
useEffect(() => {
const options = {
threshold: 0,
rootMargin: typeof offset === 'number' ? `${offset}px` : '0px'
};
// 监听元素是否进入可视区域
elementObserver.current = new IntersectionObserver(lazyLoadHandler, options);
const node = containerRef.current;
elementObserver.current.observe(node);
// 卸载时停止监听
return () => {
elementObserver.current.unobserve(node);
};
}, []);
// 元素进入可视区域后的处理
function lazyLoadHandler(entries) {
const [entry] = entries;
if (entry.isIntersecting) {
setVisible(true); // 显示真实内容
onContentVisible?.(); // 触发“内容可见”的回调
elementObserver.current.unobserve(containerRef.current); // 停止监听
}
}
// 没进入可视区域就显示占位,进入了就显示真实内容
return (
<div ref={containerRef}>
{visible ? children : placeholder}
</div>
);
}
用的时候和react-lazyload差不多,把要懒加载的内容包进去:
<MyLazyLoad placeholder={<div>加载中...</div>} offset={300}>
<img src="https://xxx.png" alt="" />
{/* 甚至可以包组件:<Demo /> */}
</MyLazyLoad>
三、“组件懒加载”+“内容懒加载”:双剑合璧
把前面的技术结合起来,就是 React 项目里的 “终极懒加载”:
import { lazy, Suspense } from 'react';
import MyLazyLoad from './MyLazyLoad';
// 组件代码懒加载
const Demo = lazy(() => import('./Demo'));
export default function App() {
return (
<div>
{/* 一堆内容... */}
{/* 组件代码+组件内容 都懒加载 */}
<Suspense fallback={<div>组件包加载中...</div>}>
<MyLazyLoad placeholder={<div>组件内容加载中...</div>}>
<Demo />
</MyLazyLoad>
</Suspense>
</div>
);
}
第一步:用户滑到 Demo 区域前,Demo的代码包不会下载;
第二步:代码包下载完成后,MyLazyLoad会等 Demo 进入可视区域,再渲染 Demo 的内容;
全程都有占位提示,用户不会看到 “空白” 或 “报错”。
四、总结
结语
普通网页的懒加载是让图片 “摸鱼”,React 的懒加载则是给组件和代码都发了 “摸鱼许可证”—— 不用一开场就全员到岗,该躺平时躺平,该干活时再发力。
就好比经营一家店,不用把所有商品都堆在门口 (初始化加载所有代码),而是把暂时没人要的商品放进仓库 (单独打包组件),等顾客问到了再取出来,摆上货架(渲染内容)—— 既省了门口的空间 (页面加载性能),又不会让顾客等太久 (优化用户体验)。
参考文章:原文链接