Skip to content

useNavigate() 只能在 Router 组件上下文中使用

问题描述

当你在 React 应用中使用 react-router-domuseNavigate 钩子时,可能会遇到以下错误:

useNavigate() may be used only in the context of a <Router> component

这个错误通常伴随着应用中所有组件消失的问题。出现这种情况的原因是 useNavigate 钩子必须在 <Router> 组件的上下文环境中使用。

根本原因

useNavigate 钩子在底层依赖于 React Router 的上下文系统。具体来说:

  • useNavigate 内部使用 useLocationNavigationContext
  • 这些上下文只能由 <Router> 组件(如 BrowserRouterHashRouter 等)提供
  • 如果组件不是 <Router> 的子孙组件,就无法访问这些上下文,从而导致错误

解决方案

方案一:将组件移至 Router 内部

最常见的解决方案是将使用 useNavigate 的组件移动到 <Router> 组件内部。

错误示例:

jsx
function App() {
  const navigate = useNavigate(); // 错误:在 Router 外部使用
  return (
    <BrowserRouter>
      {/* ... */}
    </BrowserRouter>
  );
}

正确示例:

jsx
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.jsmain.jsx)中包装整个应用:

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

jsx
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

jsx
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();
});

带路由的测试示例

jsx
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 和钩子:

jsx
// 错误:混合导入
import { BrowserRouter } from 'react-router-dom';
import { useNavigate } from 'react-router'; // 错误!

// 正确:统一导入
import { BrowserRouter, useNavigate } from 'react-router-dom';

Router 未正确包装

确保所有使用路由功能的组件都在 Router 内部:

jsx
// 错误:部分组件在 Router 外部
<BrowserRouter>
  <AuthProvider> {/* 如果 AuthProvider 使用 useNavigate */}
    <App />
  </AuthProvider>
</BrowserRouter>
<OtherComponent /> {/* 如果使用 useNavigate,会报错 */}

// 正确:所有组件都在 Router 内部
<BrowserRouter>
  <AuthProvider>
    <App />
  </AuthProvider>
  <OtherComponent /> {/* 现在可以安全使用 useNavigate */}
</BrowserRouter>

总结

useNavigate 必须在 <Router> 组件的上下文环境中使用。解决此问题的关键步骤是:

  1. 确保使用 useNavigate 的组件是 <Router> 的子孙组件
  2. 在测试中包装组件与适当的 Router(如 MemoryRouter
  3. 检查所有 react-router-dom 导入来自同一版本
  4. 避免混合使用不同包的导入

遵循这些最佳实践可以避免 "useNavigate() may be used only in the context of a <Router> component" 错误,并确保你的 React Router 应用正常工作。