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

React Router 6预加载器实现报错解决及可行方案

解决React Router 6中预加载器(Preloader)的Hook调用错误及实现方案

问题背景

你想给个人网站添加预加载器,要求首次访问任意页面时先显示加载过渡效果,但当前代码因为在函数组件外部直接使用useState和useEffect Hooks,触发了错误:Warning: Invalid hook call. Hooks can only be called inside of the body of a function component.

错误原因

React Hooks(包括useState、useEffect)必须在函数组件的顶层作用域内调用,不能放在组件外部、条件判断、循环等非组件顶层的位置。你的代码把Hook写在了全局作用域,完全违反了Hook的使用规则。

基础修复方案:封装成根组件

把预加载逻辑封装到一个函数组件内部,在组件里合法使用Hook,再控制预加载器和路由的渲染:

import React from 'react'
import ReactDOM from 'react-dom/client'
import './index.css'
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { useState, useEffect } from 'react';

// 导入组件和页面
import Header from './Header'
import ErrorPage from "./error-page";
import Home from './Home'
import Profile from "./Profile"
import Works from "./Works"
import Blogs from "./Blogs"
import Wm from "./works/web-monitization"
import Ss from "./works/scheduling-system"
import Bs from "./works/bharatSteel"
import Load from "./loader"

// 封装根组件,处理预加载逻辑
function AppRoot() {
  // 初始设为true,确保页面加载时立刻显示预加载器
  const [screenLoading, setScreenLoading] = useState(true);

  useEffect(() => {
    // 定时器控制预加载时长,可根据实际需求调整
    const loadTimer = setTimeout(() => {
      setScreenLoading(false);
    }, 1000);

    // 清理定时器,避免内存泄漏
    return () => clearTimeout(loadTimer);
  }, []);

  const router = createBrowserRouter([
    {
      path: "/",
      element: <Header />,
      errorElement: <ErrorPage />,
      children: [
        { path: "", element: <Home />, index: true },
        { path: "/profile", element: <Profile /> },
        { path: "/works", element: <Works /> },
        { path: "/works/web-monetization", element: <Wm /> },
        { path: "works/input-validaton", element: <Wm /> },
        { path: "works/scheduling-system", element: <Ss proji="scheduling-system" /> },
        { path: "works/bharat-steel", element: <Bs proji="scheduling-system" /> },
        { path: "blogs", element: <Blogs /> },
        { path: "loader", element: <Load /> }
      ]
    }
  ]);

  return (
    <div className='layout'>
      {screenLoading ? <Load /> : <RouterProvider router={router} />}
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<AppRoot />)

修复说明

  • 把所有逻辑放到AppRoot函数组件内部,符合Hook的使用规则
  • 初始screenLoading设为true,保证页面启动时直接显示预加载器
  • 增加定时器清理逻辑,防止组件卸载时出现内存泄漏

进阶实现:结合React Router 6的Loader实现页面级加载

如果需要在路由切换时也显示加载状态(不止首次加载),可以结合React Router 6的loader和useNavigation钩子实现全局路由加载:

1. 全局加载组件

创建一个监听路由导航状态的组件:

// GlobalLoader.jsx
import { useNavigation } from 'react-router-dom';
import Load from './loader';

export default function GlobalLoader() {
  const navigation = useNavigation();
  // 判断路由是否正在加载数据
  const isLoading = navigation.state === "loading";

  return isLoading ? <Load /> : null;
}

2. 集成到根路由布局

修改根路由的布局,把全局加载组件加入:

// 修改路由配置中的根element
{
  path: "/",
  element: (
    <div className='layout'>
      <Header />
      <GlobalLoader />
      <Outlet /> {/* 渲染子路由内容 */}
    </div>
  ),
  errorElement: <ErrorPage />,
  children: [/* 子路由配置不变 */]
}

3. 给需要加载的路由添加Loader

比如给首页添加异步Loader模拟数据加载:

{
  path: "",
  element: <Home />,
  index: true,
  loader: async () => {
    // 模拟API请求延迟
    await new Promise(resolve => setTimeout(resolve, 1000));
    // 返回页面需要的数据
    return { data: "首页数据" };
  }
}

这样,每当用户切换到带有Loader的路由时,全局加载组件会自动显示,直到Loader执行完成。

额外优化建议

  • 不要依赖固定定时器:实际项目中,建议等待关键资源(API数据、图片)加载完成后再关闭预加载器,而不是用固定时长
  • 优化过渡效果:给预加载器添加CSS过渡动画,让切换更平滑
  • 避免重复加载:如果只需要首次显示预加载器,可以用localStorage标记是否完成首次加载,防止刷新后重复显示

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 13:58:15