React路由拦截实现方案(React Router v5)
技术栈组合
核心依赖
[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 />;
};
通过以上补充,路由拦截系统将具备:
-
版本兼容性:完整覆盖v5/v6双版本方案
-
类型安全:强化TypeScript类型支持
-
异常韧性:增强错误边界和异步处理
-
性能保障:代码分割与缓存策略优化
-
安全防护:参数验证与网络感知能力
-
测试覆盖:可验证的关键路径测试方
码字不易,大佬们点点赞呗