React Router:待下一页完全加载后再进行页面切换
解决React Router+Transition Group页面切换等待资源加载的问题
嘿,这个场景我太熟悉了——用react-transition-group做整页切换时,最头疼的就是异步资源(图片、动态CSS)拖慢切换体验,componentDidMount确实不靠谱,因为它只管组件挂载,不管资源加载完成。给你一套实用的解决方案,分步骤来:
核心思路
先在目标页面组件内部监听所有依赖资源(图片、动态CSS)的加载状态,等所有资源就绪后,再通知路由层触发切换动画。同时在等待期间显示加载器,避免用户看到半加载的页面。
步骤1:封装资源加载监听Hook
先写一个自定义Hook,专门监听图片和动态CSS的加载完成状态,还能处理加载失败的情况:
import { useState, useEffect } from 'react'; const useResourcesLoaded = (imageUrls = [], cssUrls = []) => { const [resourcesReady, setResourcesReady] = useState(false); useEffect(() => { let loadedCount = 0; const totalResources = imageUrls.length + cssUrls.length; // 如果没有资源需要加载,直接标记为就绪 if (totalResources === 0) { setResourcesReady(true); return; } // 监听图片加载 imageUrls.forEach(url => { const img = new Image(); // 加载成功计数 img.onload = () => { loadedCount++; checkCompletion(); }; // 加载失败也计数(可以根据需求调整,比如提示错误) img.onerror = () => { console.warn(`图片加载失败: ${url}`); loadedCount++; checkCompletion(); }; img.src = url; }); // 监听动态CSS加载 cssUrls.forEach(url => { const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = url; link.onload = () => { loadedCount++; checkCompletion(); }; link.onerror = () => { console.warn(`CSS加载失败: ${url}`); loadedCount++; checkCompletion(); }; document.head.appendChild(link); }); // 检查所有资源是否加载完成 const checkCompletion = () => { if (loadedCount === totalResources) { setResourcesReady(true); } }; // 清理函数:移除动态添加的CSS,避免内存泄漏 return () => { cssUrls.forEach(url => { const existingLink = document.querySelector(`link[href="${url}"]`); if (existingLink) document.head.removeChild(existingLink); }); }; }, [imageUrls, cssUrls]); return resourcesReady; };
步骤2:在目标页面组件中使用Hook并通知路由层
在需要等待资源的页面组件里,用这个Hook监听资源,等就绪后通过回调通知路由层:
const NextPage = ({ onResourcesReady }) => { // 定义当前页面需要加载的图片和动态CSS const pageImages = ['/assets/img1.jpg', '/assets/img2.jpg']; const pageStyles = ['/styles/next-page.css']; const resourcesReady = useResourcesLoaded(pageImages, pageStyles); // 资源就绪后通知路由层 useEffect(() => { if (resourcesReady) { onResourcesReady(); } }, [resourcesReady, onResourcesReady]); // 资源未就绪时显示加载器 if (!resourcesReady) { return <div className="page-loader">正在准备页面...</div>; } // 资源就绪后渲染真实页面内容 return ( <div className="next-page-container"> <img src="/assets/img1.jpg" alt="场景图1" /> <img src="/assets/img2.jpg" alt="场景图2" /> {/* 其他页面内容 */} </div> ); }; export default NextPage;
步骤3:路由层控制切换时机
结合React Router和react-transition-group,拦截默认的路由跳转,等待目标页面资源就绪后再执行切换:
import { useState } from 'react'; import { BrowserRouter as Router, Route, Switch, useLocation, useHistory } from 'react-router-dom'; import { TransitionGroup, CSSTransition } from 'react-transition-group'; import HomePage from './HomePage'; import NextPage from './NextPage'; const AppRouter = () => { const location = useLocation(); const history = useHistory(); const [isTransitioning, setIsTransitioning] = useState(false); const [pendingLocation, setPendingLocation] = useState(null); // 自定义导航方法,代替直接history.push const navigateWithLoad = (path) => { if (isTransitioning) return; // 避免重复触发 setIsTransitioning(true); setPendingLocation(path); }; // 目标页面资源就绪后,执行路由跳转和动画 const handleResourcesReady = () => { history.push(pendingLocation); setIsTransitioning(false); setPendingLocation(null); }; return ( <div className="app-container"> {/* 导航按钮使用自定义方法 */} <nav> <button onClick={() => navigateWithLoad('/')}>首页</button> <button onClick={() => navigateWithLoad('/next')}>下一页</button> </nav> {/* 过渡组实现页面切换动画 */} <TransitionGroup> <CSSTransition key={location.key} timeout={600} classNames="page-fade" // 对应你的CSS动画类名 unmountOnExit > <Switch location={location}> <Route exact path="/" component={HomePage} /> {/* 用render传递回调给目标页面 */} <Route path="/next" render={() => <NextPage onResourcesReady={handleResourcesReady} />} /> </Switch> </CSSTransition> </TransitionGroup> {/* 全局加载遮罩,切换期间显示 */} {isTransitioning && ( <div className="global-loader-overlay"> <div className="loader-spinner"></div> </div> )} </div> ); }; const App = () => ( <Router> <AppRouter /> </Router> ); export default App;
额外说明
- 静态CSS处理:如果你的CSS是通过
import './styles.css'这种方式引入的,Webpack会把它们打包到主样式文件中,组件挂载时已经加载完成,不需要在Hook中监听。只有动态加载的CSS才需要处理。 - Suspense结合:如果你的组件是懒加载的(
React.lazy),可以用Suspense处理组件代码加载,但它不负责监听图片资源,所以还是需要结合上面的资源监听Hook。 - 错误处理:Hook中已经加入了加载失败的日志,你可以根据需求改成错误提示,或者直接忽略失败继续切换页面。
内容的提问来源于stack exchange,提问作者rfranklin
相关产品推荐
相关产品推荐

