React中BrowserRouter上下文在孙组件丢失,useNavigate报错排查
问题:React Router上下文与Hook调用冲突问题
我在React项目中原本用BrowserRouter包裹ErrorBoundary和App组件,App组件在开发环境下使用useNavigate钩子能正常运行,但ESLint提示React Hook不能被条件式调用。为解决这个问题,我把开发环境逻辑拆分到MySecurityComponent及其子组件MySecurityComponentDev中,却出现错误:useNavigate() may be used only in the context of a <Router> component。想请教是不是遗漏了上下文传递步骤,或是Router上下文只能在顶层子组件和路由内使用?
相关代码
初始化代码
ReactDOM.createRoot(document.getElementById('root')!).render( <React.StrictMode> <BrowserRouter> <ErrorBoundary FallbackComponent={CustomError}> <App /> </ErrorBoundary> </BrowserRouter> </React.StrictMode>, )
初始App组件(ESLint报错版本)
export const App = () => { if (isDevEnv()) { const navigate = useNavigate(); const restoreOriginalUri = async (originalUri: string) => { navigate(toRelativeUrl(originalUri || '/', window.location.origin)); }; return ( <Security restoreOriginalUri={restoreOriginalUri}> <AppRoutes /> </Security> ) } return ( <AppRoutes /> ) }
拆分后的App组件
export const App = () => { const children: React.JSX.Element = <AppRoutes />; return <MySecurityComponent children={children} /> }
MySecurityComponent组件
type MySecurityComponentType = (props: { children: React.JSX.Element }) => React.JSX.Element; export const MySecurityComponent: MySecurityComponentType = ({children}): React.JSX.Element => { if (isDevEnv()) { return <MySecurityComponentDev children={children} /> } return children; }
MySecurityComponentDev组件
type MySecurityComponentDevType = (props: { children: React.JSX.Element }) => React.JSX.Element; const MySecurityComponentDev: MySecurityComponentDevType = ({ children }) => { const navigate = useNavigate(); const restoreOriginalUri = async (originalUri: string) => { navigate(toRelativeUrl(originalUri || '/', window.location.origin)); }; return ( <Security restoreOriginalUri={restoreOriginalUri}> {children} </Security> ); }
解决方案
问题核心
你遇到的错误并非Router上下文传递遗漏,而是提前渲染了AppRoutes并作为已完成的JSX元素传递,导致组件上下文绑定时机异常。同时Router上下文可传递给所有内部嵌套组件,不存在层级限制。
修正方案一:内联组件处理开发逻辑
将开发环境逻辑封装为内部组件,确保useNavigate在组件顶层调用,既符合Hook规则,又保证组件处于Router上下文内:
export const App = () => { // 开发环境专用组件,Hook在顶层调用 const DevSecurityWrapper = () => { const navigate = useNavigate(); const restoreOriginalUri = async (originalUri: string) => { navigate(toRelativeUrl(originalUri || '/', window.location.origin)); }; return ( <Security restoreOriginalUri={restoreOriginalUri}> <AppRoutes /> </Security> ); }; return isDevEnv() ? <DevSecurityWrapper /> : <AppRoutes />; };
修正方案二:调整组件传递方式(适配你的拆分思路)
修改组件接收的children为组件类型而非已渲染元素,确保AppRoutes在正确的上下文内渲染:
- 修改
MySecurityComponent:
// 类型定义改为接收组件而非已渲染元素 type MySecurityComponentType = (props: { children: React.ComponentType }) => React.JSX.Element; export const MySecurityComponent: MySecurityComponentType = ({ children: ChildrenComponent }) => { if (isDevEnv()) { return <MySecurityComponentDev> <ChildrenComponent /> </MySecurityComponentDev>; } return <ChildrenComponent />; };
- 修改
App组件:
export const App = () => { // 传递组件本身,而非已渲染的JSX元素 return <MySecurityComponent children={AppRoutes} />; };
MySecurityComponentDev保持不变(仅调整类型适配ReactNode):
type MySecurityComponentDevType = (props: { children: React.ReactNode }) => React.JSX.Element; const MySecurityComponentDev: MySecurityComponentDevType = ({ children }) => { const navigate = useNavigate(); const restoreOriginalUri = async (originalUri: string) => { navigate(toRelativeUrl(originalUri || '/', window.location.origin)); }; return ( <Security restoreOriginalUri={restoreOriginalUri}> {children} </Security> ); };
内容的提问来源于stack exchange,提问作者Andrei Isakov
相关产品推荐
相关产品推荐

