useNavigate() 只能在 Router 组件上下文中使用
问题描述
当你在 React 应用中使用 react-router-dom
的 useNavigate
钩子时,可能会遇到以下错误:
useNavigate() may be used only in the context of a <Router> component
这个错误通常伴随着应用中所有组件消失的问题。出现这种情况的原因是 useNavigate
钩子必须在 <Router>
组件的上下文环境中使用。
根本原因
useNavigate
钩子在底层依赖于 React Router 的上下文系统。具体来说:
useNavigate
内部使用useLocation
和NavigationContext
- 这些上下文只能由
<Router>
组件(如BrowserRouter
、HashRouter
等)提供 - 如果组件不是
<Router>
的子孙组件,就无法访问这些上下文,从而导致错误
解决方案
方案一:将组件移至 Router 内部
最常见的解决方案是将使用 useNavigate
的组件移动到 <Router>
组件内部。
错误示例:
function App() {
const navigate = useNavigate(); // 错误:在 Router 外部使用
return (
<BrowserRouter>
{/* ... */}
</BrowserRouter>
);
}
正确示例:
function AppContent() {
const navigate = useNavigate(); // 正确:在 Router 内部使用
return (
<div>
<button onClick={() => navigate(-1)}>go back</button>
<Nav />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/home" element={<Home />} />
<Route path="/upcoming/:user" element={<Upcoming />} />
<Route path="/record/:user" element={<Record />} />
<Route path="*" element={<NotFound />} />
</Routes>
</div>
);
}
function App() {
return (
<BrowserRouter>
<AppContent />
</BrowserRouter>
);
}
方案二:在入口文件中包装 Router
另一种常见模式是在应用的入口文件(如 index.js
或 main.jsx
)中包装整个应用:
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
测试环境中的解决方案
在单元测试中,你同样需要为使用 useNavigate
的组件提供 Router 上下文。
使用 MemoryRouter
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import MyComponent from './MyComponent';
test('renders component', () => {
render(
<MemoryRouter>
<MyComponent />
</MemoryRouter>
);
expect(screen.getByText('Some text')).toBeInTheDocument();
});
使用 BrowserRouter
import { render, screen } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import MyComponent from './MyComponent';
test('renders component', () => {
render(
<BrowserRouter>
<MyComponent />
</BrowserRouter>
);
expect(screen.getByText('Some text')).toBeInTheDocument();
});
带路由的测试示例
import { render, screen } from '@testing-library/react';
import { MemoryRouter, Routes, Route } from 'react-router-dom';
import UserProfile from './UserProfile';
test('renders user profile', () => {
render(
<MemoryRouter initialEntries={['/user/123']}>
<Routes>
<Route path="/user/:id" element={<UserProfile />} />
</Routes>
</MemoryRouter>
);
expect(screen.getByText('User Profile')).toBeInTheDocument();
});
常见陷阱
版本不一致
WARNING
确保所有 react-router-dom
相关导入来自同一版本。混合使用 v5 和 v6 会导致此错误。
错误的导入路径
DANGER
不要从不同的包导入 Router 和钩子:
// 错误:混合导入
import { BrowserRouter } from 'react-router-dom';
import { useNavigate } from 'react-router'; // 错误!
// 正确:统一导入
import { BrowserRouter, useNavigate } from 'react-router-dom';
Router 未正确包装
确保所有使用路由功能的组件都在 Router 内部:
// 错误:部分组件在 Router 外部
<BrowserRouter>
<AuthProvider> {/* 如果 AuthProvider 使用 useNavigate */}
<App />
</AuthProvider>
</BrowserRouter>
<OtherComponent /> {/* 如果使用 useNavigate,会报错 */}
// 正确:所有组件都在 Router 内部
<BrowserRouter>
<AuthProvider>
<App />
</AuthProvider>
<OtherComponent /> {/* 现在可以安全使用 useNavigate */}
</BrowserRouter>
总结
useNavigate
必须在 <Router>
组件的上下文环境中使用。解决此问题的关键步骤是:
- 确保使用
useNavigate
的组件是<Router>
的子孙组件 - 在测试中包装组件与适当的 Router(如
MemoryRouter
) - 检查所有
react-router-dom
导入来自同一版本 - 避免混合使用不同包的导入
遵循这些最佳实践可以避免 "useNavigate() may be used only in the context of a <Router> component" 错误,并确保你的 React Router 应用正常工作。