You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;

额外说明

  1. 静态CSS处理:如果你的CSS是通过import './styles.css'这种方式引入的,Webpack会把它们打包到主样式文件中,组件挂载时已经加载完成,不需要在Hook中监听。只有动态加载的CSS才需要处理。
  2. Suspense结合:如果你的组件是懒加载的(React.lazy),可以用Suspense处理组件代码加载,但它不负责监听图片资源,所以还是需要结合上面的资源监听Hook。
  3. 错误处理:Hook中已经加入了加载失败的日志,你可以根据需求改成错误提示,或者直接忽略失败继续切换页面。

内容的提问来源于stack exchange,提问作者rfranklin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 10:25:20