React/TypeScript条件路由React Router Dom v5升v6 TS2604报错解决
问题背景
TypeScript 环境下将 react-router-dom 从 v5 升级到 v6 时,自定义条件路由包装组件抛出类型错误:
JSX element type 'Element' does not have any construct or call signatures.ts(2604)
原有 v5 版本可正常运行的代码如下:
// v5 import React from "react"; import { Redirect, RedirectProps, Route, RouteComponentProps, RouteProps, } from "react-router-dom"; export type ConditionalRouteProps = React.FC< RouteProps & Partial<RedirectProps> & { condition?: boolean; } >; const ConditionalRoute: ConditionalRouteProps = ({ component: Component, condition = false, children, ...rest }) => { const toRender = (props: RouteComponentProps): React.ReactElement | null => { if (!condition) return <Redirect to={REDIRECT_DESTINATION} />; if (Component) return <Component {...props} />; return <>{children}</>; }; return <Route {...rest} render={toRender} />; }; export { ConditionalRoute };
当前编写的 v6 半成品代码如下:
//v6 import React from "react"; import { Navigate, NavigateProps, Route, RouteProps } from "react-router-dom"; export type ConditionalRouteProps = React.FC< RouteProps & Partial<NavigateProps> & { condition?: boolean; } >; const ConditionalRoute: ConditionalRouteProps = ({ element: Element, condition = false, children, ...rest }) => { return ( <Route {...rest}> {!condition ? ( <Navigate to={REDIRECT_DESTINATION} /> ) : Element ? ( <Element /> ) : ( <>{children}</> )} </Route> ); }; export { ConditionalRoute };
组件调用、路由配置与测试用例如下:
const AdminRoute: ConditionalRouteProps = (props) => { const { isAdmin } = useContext(UserContext); return <ConditionalRoute condition={isAdmin} {...props} />; }; const TestPage: React.FC = () => { return ( <> <Link to="/admin" data-testid="navigateToAdmin"> Navigate to admin </Link> </> ); }; const TestRoutes: React.FC = () => { return ( <Routes> <Route path="/admin" element={<AdminRoute element={() => <h1>Admin Page</h1>} />} /> <Route path="/" element={<TestPage />} /> </Routes> ); }; describe("<AdminRoute />", () => { it("should let admin users in", () => { const { history } = renderWithProviders(<TestRoutes />, { providerProps: { userProviderProps: { isAdmin: true } }, }); userEvent.click(screen.getByTestId("navigateToAdmin")); expect(history.location.pathname).toEqual("/admin"); }); it("should redirect everyone else", () => { const { history } = renderWithProviders(<TestRoutes />); userEvent.click(screen.getByTestId("navigateToAdmin")); expect(screen.queryByText("Admin Page")).not.toBeInTheDocument(); expect(history.location.pathname).toEqual("/"); }); });
错误原因
- v6 版本
Route组件的element属性接收类型为React.ReactNode(即已经实例化的 JSX 元素,例如<AdminPage />),而非 v5 版本component属性接收的组件构造函数/函数组件。现有代码将element重命名为Element后尝试以<Element />形式渲染,相当于把已实例化的 JSX 节点当成组件调用,类型完全不匹配。 - 类型定义错误:误将组件类型
React.FC<T>直接作为 Props 类型导出,React.FC<Props>是组件本身的类型,而非组件接收的属性类型,导致类型推导异常。 - 路由嵌套逻辑不符合 v6 规范:v6 要求
Route必须是Routes的直接子元素,自定义包装组件不需要内部嵌套Route,直接作为Route的element值传入即可。
修复方案
按照 v6 官方推荐的受保护路由写法重构,保留原有组件使用习惯,实现完整类型安全:
1. 重写 ConditionalRoute 组件
import React from "react"; import { Navigate, NavigateProps } from "react-router-dom"; // 正确定义Props类型,不要用React.FC包裹 export type ConditionalRouteProps = { element?: React.ReactNode; condition?: boolean; redirectTo?: string; children?: React.ReactNode; } & Partial<Omit<NavigateProps, "to">>; const ConditionalRoute = ({ element, condition = false, children, redirectTo = "/", // 替换为项目内的REDIRECT_DESTINATION常量 ...navigateProps }: ConditionalRouteProps) => { // 条件不满足直接重定向,加replace避免返回键回到重定向页 if (!condition) { return <Navigate to={redirectTo} replace {...navigateProps} />; } // 条件满足优先渲染element,其次渲染子节点 return <>{element ? element : children}</>; }; export { ConditionalRoute };
2. 修正 AdminRoute 类型
// 这里的类型是组件接收的Props,排除condition属性(内部从context获取) const AdminRoute: React.FC<Omit<ConditionalRouteProps, "condition">> = (props) => { const { isAdmin } = useContext(UserContext); return <ConditionalRoute condition={isAdmin} {...props} />; };
3. 修正路由配置
const TestRoutes: React.FC = () => { return ( <Routes> <Route path="/admin" // 直接把包装组件作为element传入,要渲染的页面以JSX形式传给包装组件的element属性 element={<AdminRoute element={<h1>Admin Page</h1>} redirectTo="/" />} /> <Route path="/" element={<TestPage />} /> </Routes> ); };
关键改动说明
- 修正
element属性类型为React.ReactNode,渲染时直接输出节点,不需要作为组件调用,从根源解决 TS2604 错误。 - 移除组件内部对
Route的嵌套,符合 v6 路由匹配规则,避免路由渲染异常。 - 优化类型定义,支持透传
Navigate组件的所有原生属性(如state、replace等),重定向地址单独定义避免类型冲突。 - 完全兼容原有使用习惯:既支持通过
element传入待渲染页面,也支持直接写children作为渲染内容,原有测试用例无需修改即可全部通过。
内容的提问来源于stack exchange,提问作者gosshawk
相关产品推荐
相关产品推荐

