React客户端渲染:如何预加载资源并显示全局加载页?
在React路由外层实现全局资源预加载与加载页
核心思路
在路由最外层封装一个全局加载组件,统一处理自定义字体预加载和页面所需图片预加载,通过状态控制加载页与路由内容的切换,无需在每个组件内重复编写加载逻辑。
实现步骤与示例代码
1. 全局预加载组件 ResourceLoader
该组件负责加载指定资源,监听所有资源的加载状态,完成后渲染路由内容:
import { useState, useEffect } from 'react'; // 定义需要预加载的核心资源列表 const PRELOAD_RESOURCES = { // 自定义字体:键为字体名称,值为字体文件路径 fonts: [ { name: 'YourCustomFont', src: '/fonts/your-custom-font.woff2' }, { name: 'SecondaryFont', src: '/fonts/secondary-font.woff2' } ], // 首屏及核心页面需要的图片 images: [ '/images/hero-banner.jpg', '/images/site-logo.png' ] }; const ResourceLoader = ({ children }) => { const [allLoaded, setAllLoaded] = useState(false); const [hasError, setHasError] = useState(false); useEffect(() => { let isComponentMounted = true; const loadTasks = []; // 加载自定义字体 PRELOAD_RESOURCES.fonts.forEach(font => { const fontFace = new FontFace(font.name, `url(${font.src})`); const fontLoadTask = fontFace.load() .then(loadedFont => document.fonts.add(loadedFont)) .catch(err => { console.error(`字体 ${font.name} 加载失败:`, err); setHasError(true); }); loadTasks.push(fontLoadTask); }); // 预加载图片 PRELOAD_RESOURCES.images.forEach(imgUrl => { const imgLoadTask = new Promise((resolve, reject) => { const img = new Image(); img.src = imgUrl; img.onload = resolve; img.onerror = () => { console.error(`图片 ${imgUrl} 加载失败`); reject(); }; }); loadTasks.push(imgLoadTask); }); // 等待所有资源加载完成(允许部分失败,根据业务调整) Promise.allSettled(loadTasks) .then(results => { if (!isComponentMounted) return; const failedTasks = results.filter(res => res.status === 'rejected'); if (failedTasks.length) setHasError(true); setAllLoaded(true); }); // 组件卸载时取消状态更新 return () => { isComponentMounted = false; }; }, []); // 加载中显示加载页 if (!allLoaded && !hasError) { return ( <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh', backgroundColor: '#f8f9fa' }}> <div style={{ fontSize: '1.2rem', color: '#6c757d' }}>页面加载中...</div> {/* 可替换为自定义加载动画 */} </div> ); } // 资源加载失败时显示降级提示 if (hasError) { return ( <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh', color: '#dc3545' }}> <div>部分资源加载失败,请刷新页面重试</div> </div> ); } // 所有资源加载完成,渲染路由内容 return children; }; export default ResourceLoader;
2. 在路由外层包裹该组件
以react-router-dom为例,在路由配置的最外层嵌套ResourceLoader:
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; import ResourceLoader from './ResourceLoader'; import Home from './pages/Home'; import About from './pages/About'; function App() { return ( <Router> <ResourceLoader> <Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> {/* 其他路由配置 */} </Routes> </ResourceLoader> </Router> ); } export default App;
额外说明
- 动态资源处理:如果部分页面的图片/字体是接口返回的动态资源,可以在对应页面的
useEffect中补充预加载逻辑,或在ResourceLoader中根据当前路由动态更新资源列表。 - 字体加载兜底:若希望在字体加载失败时使用系统字体,可在CSS中为元素设置
font-family: YourCustomFont, sans-serif;作为降级方案。 - 性能优化:仅预加载首屏和核心资源,非核心资源可使用懒加载(如
IntersectionObserver)避免延长初始加载时间。
内容的提问来源于stack exchange,提问作者Chloe
相关产品推荐
相关产品推荐

