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

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

技术栈组合

核心依赖

[email protected]

[email protected]

[email protected]

[email protected]

[email protected] # 用于弹框组件

方式一:使用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. 测试覆盖:可验证的关键路径测试方

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

相关推荐
red润6 分钟前
npm包autocannon牛逼的后台压力测试库
前端·javascript·node.js
黄蘑菇6 分钟前
white-space、word-break、overflow-wrap(原名word-wrap)的区别
前端
渔樵江渚上9 分钟前
JavaScript函数柯里化:优雅的函数式编程实践
前端·javascript·面试
the_one10 分钟前
如何判断一个属性是否存在
前端·javascript·面试
张开心_kx12 分钟前
5202年了,还不懂原型链吗?
前端
用户6133467165312 分钟前
开发体育赛事直播系统:炫彩弹幕直播间界面技术实现方案
前端·后端
恋猫de小郭13 分钟前
Android 转内部开发谁说是闭源?明明 AOSP 外部 PR 支持也会继续
android·前端·flutter
code bean16 分钟前
【C#】`Task.Factory.StartNew` 和 `Task.Run` 区别
前端·vue.js·c#
倔强青铜三27 分钟前
WXT浏览器插件开发中文教程(9)----WXT配置详解之Vite配置
前端·javascript·vue.js
倔强青铜三29 分钟前
WXT浏览器插件开发中文教程(10)----WXT配置详解之构建模式
前端·javascript·vue.js