【react】实现路由返回拦截的多种方式

React路由拦截实现方案(React Router v5)

技术栈组合

核心依赖

react@17.x

react-dom@17.x

react-router-dom@5.3.4

history@4.10.1

antd@4.24.8 # 用于弹框组件

方式一:使用history.block实现

javascript 复制代码
import { useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import { Modal } from 'antd';

function RouteGuard() {
  const history = useHistory();

  useEffect(() => {
    // 创建路由拦截器
    const unblock = history.block((tx) => {
      Modal.confirm({
        title: '离开确认',
        content: '有未保存的更改,确定要离开吗?',
        onOk: () => {
          unblock(); // 先解除拦截
          tx.retry(); // 重试跳转
        }
      });
      return false; // 阻止默认跳转
    });

    return () => unblock(); // 组件卸载时解除拦截
  }, [history]);

  return null;
}

// 使用:在需要拦截的页面组件中引入

方式二:使用prompt结合message函数实现自定义弹框

javascript 复制代码
import { Prompt } from 'react-router-dom';
import { Modal } from 'antd';

let showModal = null;

function CustomPrompt() {
  const [isBlocking, setIsBlocking] = useState(false);

  return (
    <>
      <Prompt
        when={isBlocking}
        message={(location) => {
          Modal.confirm({
            title: '路由拦截',
            content: `即将跳转到 ${location.pathname}`,
            onOk: () => setIsBlocking(false),
            onCancel: () => setIsBlocking(true)
          });
          return false; // 必须返回布尔值
        }}
      />
    </>
  );
}

方式三:自定义全局拦截器

基于prompt,修改路由getUserConfirmation实现自定义弹框拦截

javascript 复制代码
// 入口文件index.js
import { BrowserRouter } from 'react-router-dom';
import { Modal } from 'antd';

<BrowserRouter
  getUserConfirmation={(message, callback) => {
    Modal.confirm({
      title: '系统提示',
      content: message,
      onOk: () => callback(true),
      onCancel: () => callback(false)
    });
  }}
>
  <App />
</BrowserRouter>

// 页面组件中使用
import { Prompt } from 'react-router-dom';

function EditPage() {
  return (
    <Prompt
      when={true}
      message="当前页面有未保存内容,确定离开?"
    />
  );
}

方式四:动态路由控制

javascript 复制代码
// 路由配置
const dynamicRoutes = [
  {
    path: '/dashboard',
    component: Dashboard,
    auth: true
  },
  {
    path: '/login',
    component: Login
  }
];

// 路由守卫组件
function AuthRoute({ component: Component, ...rest }) {
  const isLogin = !!localStorage.getItem('token');
  
  return (
    <Route
      {...rest}
      render={(props) =>
        isLogin ? (
          <Component {...props} />
        ) : (
          <Redirect
            to={{ pathname: '/login', state: { from: props.location } }}
          />
        )
      }
    />
  );
}

// 动态路由渲染
function AppRouter() {
  return (
    <Switch>
      {dynamicRoutes.map((route) => (
        route.auth ? (
          <AuthRoute key={route.path} {...route} />
        ) : (
          <Route key={route.path} {...route} />
        )
      ))}
    </Switch>
  );
}

方案对比

方案 优势 适用场景 注意事项
方案一 细粒度控制,支持复杂交互逻辑 需要动态解除拦截的场景 需手动管理拦截器生命周期
方案二 组件化集成,简单快捷 简单弹窗确认场景 需要处理状态同步问题
方案三 全局统一拦截策略,架构清晰 需要统一拦截样式的项目 需在入口文件配置
方案四 完善的权限路由体系 需要动态路由控制的企业级应用 需维护路由配置元数据

最佳实践建议

混合使用策略:在大型项目中组合使用方案三(全局拦截)和方案四(权限路由)

性能优化:对于需要频繁拦截的页面,优先使用方案一(细粒度控制)

状态管理:建议配合Redux/Zustand管理路由拦截状态

异常处理:所有路由跳转需添加错误边界处理

TypeScript支持:推荐使用类型化的路由配置:

javascript 复制代码
interface RouteConfig {
  path: string;
  component: React.ComponentType;
  auth?: boolean;
  children?: RouteConfig[];
}

扩展能力

埋点拦截:在路由守卫中添加用户行为追踪

javascript 复制代码
history.listen((location) => {
  trackPageView(location.pathname);
});

页面缓存 :结合<KeepAlive>实现路由级缓存

动态加载 :配合React.lazy实现按需加载

javascript 复制代码
const Dashboard = React.lazy(() => import('./Dashboard'));

多级路由守卫:实现全局守卫+页面级守卫的多层校验体系

通过合理选择路由拦截方案,开发者可以在保证用户体验的同时,构建出安全可靠的前端路由系统。建议根据项目规模选择基础方案(中小项目用方案三)或组合方案(大型项目用方案三+四)

以上主要是用react router v5的方案

以下是react router v6

React路由拦截实现方案(React Router v5)

一、React Router v6适配要点

API变更说明

// v5 -> v6 核心变化

  • <Switch> → <Routes>

  • <Redirect> → <Navigate>

  • useHistory → useNavigate

  • withRouter移除,推荐使用Hooks

v6拦截方案示例

javascript 复制代码
// 权限路由组件(v6版本)
const ProtectedRoute = ({ children }) => {
  const { user } = useAuth();
  const location = useLocation();
  
  return user ? children : (
    <Navigate to="/login" state={{ from: location }} replace />
  );
};

// 路由配置
<Route path="/dashboard" element={
  <ProtectedRoute>
    <Dashboard />
  </ProtectedRoute>
} />
二、React Router v6数据加载API

Loader拦截示例

javascript 复制代码
// 路由定义
const router = createBrowserRouter([
  {
    path: "/dashboard",
    element: <Dashboard />,
    loader: async () => {
      const data = await fetchUserData();
      if (!data.valid) throw new Response("Unauthorized", { status: 401 });
      return data;
    },
    errorElement: <ErrorPage />
  }
]);

// 统一错误处理
<RouterProvider router={router} />
三、v6版本Prompt替代方案

自定义拦截逻辑

javascript 复制代码
import { unstable_useBlocker as useBlocker } from 'react-router-dom';

const NavigationBlock = ({ when }) => {
  useBlocker((tx) => {
    if (when && !window.confirm('确定离开当前页面?')) {
      tx.retry();
    }
  });
  return null;
};

// 使用
<NavigationBlock when={formIsDirty} />
四、状态管理深度集成

Zustand集成示例

javascript 复制代码
// store/authStore.js
import create from 'zustand';

const useAuthStore = create(set => ({
  token: null,
  login: (token) => set({ token }),
  logout: () => set({ token: null })
}));

// 路由组件
const AuthRoute = ({ children }) => {
  const token = useAuthStore(state => state.token);
  return token ? children : <Navigate to="/login" />;
};
五、增强型错误处理

异步操作拦截策略

javascript 复制代码
const SaveBeforeLeave = () => {
  const blocker = useBlocker(({ currentLocation, nextLocation }) => 
    currentLocation.pathname !== nextLocation.pathname
  );

  useEffect(() => {
    if (blocker.state === "blocked") {
      (async () => {
        try {
          await autoSave();
          blocker.proceed();
        } catch (error) {
          blocker.reset();
        }
      })();
    }
  }, [blocker]);
};
六、测试策略

Jest测试示例

javascript 复制代码
test('拦截未授权访问', async () => {
  const TestComponent = () => {
    const nav = useNavigate();
    useEffect(() => { nav('/protected') }, []);
    return null;
  };

  render(
    <MemoryRouter>
      <Routes>
        <Route path="/protected" element={<ProtectedRoute><div>Protected</div></ProtectedRoute>} />
        <Route path="/login" element={<div>Login</div>} />
      </Routes>
      <TestComponent />
    </MemoryRouter>
  );

  await waitFor(() => expect(screen.getByText('Login')).toBeInTheDocument());
});
七、TypeScript增强

完整类型定义

javascript 复制代码
interface RouteMeta {
  requiresAuth?: boolean;
  permissions?: string[];
}

declare module 'react-router-dom' {
  interface RouteObject {
    meta?: RouteMeta;
    children?: RouteObject[];
  }
}

const routes: RouteObject[] = [
  {
    path: '/admin',
    element: <AdminPage />,
    meta: {
      requiresAuth: true,
      permissions: ['ADMIN']
    }
  }
];
八、安全增强措施

路由参数验证

javascript 复制代码
const ProductRoute = () => {
  const { id } = useParams();
  const isValid = /^\d+$/.test(id);

  return isValid ? <ProductPage /> : (
    <Navigate to="/404" replace />
  );
};

升级路线图

版本 核心方案 升级建议
v5 Prompt + 高阶组件 逐步替换为v6方案
v6.0-6.3 useBlocker实验性API 配合状态管理实现
v6.4+ 数据路由 + Loader拦截 推荐作为新项目首选方案

性能优化补充

代码分割

javascript 复制代码
const Dashboard = lazy(() => import('./Dashboard'));
<Route path="/dashboard" element={
  <Suspense fallback={<Spinner />}>
    <ProtectedRoute>
      <Dashboard />
    </ProtectedRoute>
  </Suspense>
} />

缓存策略

javascript 复制代码
const CachedRoute = ({ children }) => {
  const elementRef = useRef(children);
  return <div hidden={!isVisible}>{elementRef.current}</div>;
};

移动端专项优化

手势拦截

javascript 复制代码
useEffect(() => {
  const preventBack = (e) => {
    e.preventDefault();
    showConfirmModal();
  };
  
  history.pushState(null, '', window.location.pathname);
  window.addEventListener('popstate', preventBack);
  
  return () => window.removeEventListener('popstate', preventBack);
}, []);

离线处理

javascript 复制代码
const NetworkAwareRoute = ({ children }) => {
  const [online] = useNetworkStatus();
  return online ? children : <OfflinePage />;
};

通过以上补充,路由拦截系统将具备:

  1. 版本兼容性:完整覆盖v5/v6双版本方案

  2. 类型安全:强化TypeScript类型支持

  3. 异常韧性:增强错误边界和异步处理

  4. 性能保障:代码分割与缓存策略优化

  5. 安全防护:参数验证与网络感知能力

  6. 测试覆盖:可验证的关键路径测试方

码字不易,大佬们点点赞呗

相关推荐
仰望星空的小猴子1 分钟前
React18和React19新特性
前端
小码哥_常2 分钟前
Android新航标:Navigation 3为何成为变革先锋?
前端
SuperEugene3 分钟前
Vue状态管理扫盲篇:状态管理中的常见坑 | 循环依赖、状态污染与调试技巧
前端·vue.js·面试
骑着小黑马4 分钟前
从 Electron 到 Tauri 2:我用 3.5MB 做了个音乐播放器
前端·vue.js·typescript
aykon4 分钟前
DataSource详解以及优势
前端
Mintopia5 分钟前
戴了 30 天智能手环后,我才发现自己一直低估了“睡眠”
前端
leolee185 分钟前
react redux 简单使用
前端·react.js·redux
仰望星空的小猴子6 分钟前
常用的Hooks
前端
天才熊猫君6 分钟前
Vue Fragment 锚点机制
前端
米丘7 分钟前
Git 常用操作命令
前端