You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

发布至npm的可复用React Dashboard组件中useNavigate使用报错的解决方案咨询

发布至npm的可复用React Dashboard组件中useNavigate使用报错的解决方案咨询

我最近踩了个坑——自己开发的可复用React Dashboard组件包,在本地项目直接引用时用useNavigate完全正常,但发布到npm后,其他开发者安装使用就会抛出Uncaught Error: useNavigate() may be used only in the context of a <Router> component的错误,折腾了好一阵才捋清楚原因和解决办法,这里分享给大家。

问题场景还原

我开发的leximo-dashboard包内有个LeftMenu组件,内部依赖react-router-dom的useNavigate钩子处理菜单导航,本地直接调试组件时毫无问题:

// 组件内部LeftMenu核心代码
import { useNavigate } from 'react-router-dom';
const LeftMenu = ({ menuItems }) => {
  const navigate = useNavigate();

  const handleMenuClick = (item) => {
    // 把navigate实例传给外部传入的action
    item.action({ navigate, closeMenu: () => {/* 菜单关闭逻辑 */} });
  };

  // ...组件渲染逻辑
};

但当用户从npm安装后,在他们的项目中按常规方式使用就会报错,用户的项目代码示例如下:

// 用户项目的App.js代码
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { Dashboard, ThemeProvider } from 'leximo-dashboard';
import 'leximo-dashboard/dist/assets/style.css';
import AuthProvider from "./Providers/AuthProvider";

export default function App() {
  return (
    <AuthProvider>
      <ThemeProvider>
        <Router>
          <AppContent />
        </Router>
      </ThemeProvider>
    </AuthProvider>
  );
}

function AppContent() {
  const { user } = useAuth();
  const menuItems = [
    { label: 'Home', icon: 'home', action: ({ navigate }) => navigate('/') }
  ];

  return (
    <Routes>
      <Route path="/" element={<Dashboard menuItems={menuItems} />} />
      {/* 其他路由配置 */}
    </Routes>
  );
}

问题根源

这个错误的核心原因是React Router的上下文隔离:
我最初把react-router-dom放在了组件包的dependencies里,这会导致组件包打包时把react-router-dom的代码一起打进包内。当用户的项目也安装了react-router-dom时,就会出现两个不同的react-router-dom实例——组件包用自己的实例,用户项目用本地的实例。

而useNavigate必须在同一个Router实例的上下文中才能工作,组件包内部的useNavigate找不到用户项目中Router提供的上下文,自然就抛出了错误。

可行解决方案

方案1:将react-router-dom设为peerDependencies(推荐)

这是最标准的解决方案,可复用组件不应该自带react-router-dom这类全局依赖,而是要求宿主项目提供兼容的版本,确保组件和宿主项目共享同一个依赖实例。

具体操作:

  1. 修改组件包的package.json,把react-router-dom从dependencies移到peerDependencies,同时指定兼容的版本范围:
{
  "name": "leximo-dashboard",
  // ...其他配置
  "peerDependencies": {
    "react-router-dom": "^6.0.0"
  },
  "devDependencies": {
    "react-router-dom": "^6.0.0" // 开发调试时需要安装
  }
}
  1. 重新执行npm install,然后重新打包组件并发布到npm。

这样用户安装你的组件时,npm会自动检查他们项目中是否有符合版本要求的react-router-dom,如果没有会提示用户安装,确保组件和用户项目使用同一个依赖实例,上下文就能正常共享了。

方案2:让用户传入navigate实例(降级/灵活方案)

如果需要让组件支持不依赖react-router-dom的场景,或者给用户更多灵活性,可以把navigate实例作为props从外部传入组件,而不是在组件内部使用useNavigate。

修改组件内部的LeftMenu和Dashboard:

// 改造后的LeftMenu,从props接收navigate
const LeftMenu = ({ menuItems, navigate }) => {
  const handleMenuClick = (item) => {
    item.action({ navigate, closeMenu: () => {/* 菜单关闭逻辑 */} });
  };
  // ...渲染逻辑
};

// 对应的Dashboard组件也需要接收navigate并传递给LeftMenu
const Dashboard = ({ menuItems, topBarConfig, navigate }) => {
  return (
    <div className="dashboard-container">
      <LeftMenu menuItems={menuItems} navigate={navigate} />
      {/* 顶部栏等其他组件 */}
    </div>
  );
};

用户在使用时,自己通过useNavigate拿到实例后传入Dashboard:

// 用户项目的AppContent组件
import { useNavigate } from 'react-router-dom';

function AppContent() {
  const { user } = useAuth();
  const navigate = useNavigate();
  const menuItems = [
    { label: 'Home', icon: 'home', action: ({ navigate }) => navigate('/') }
  ];

  return (
    <Routes>
      <Route path="/" element={<Dashboard menuItems={menuItems} navigate={navigate} />} />
    </Routes>
  );
}

这个方案虽然增加了用户的使用成本,但能让组件脱离react-router-dom的上下文限制,适合需要更灵活导航逻辑的场景。

总结

遇到这类依赖上下文的报错,首先要想到是否存在多依赖实例的问题。对于React Router这类需要共享上下文的库,将其设为peerDependencies是可复用组件的标准做法,能从根源上解决上下文隔离的问题。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 13:38:00