TypeScript中如何正确解构混合React props与自定义props
问题描述
需要封装基于react-router-dom的受保护路由组件,需求如下:
- 接收原生
<Route>组件的标准element属性 - 支持三个自定义属性:
feature(功能开关标识)、role(访问所需角色)、fallback(无权限时的回退内容/跳转地址) - 其余所有原生Route属性需完整透传给内部包装的Route组件
原有实现代码抛出TypeScript错误:An interface can only extend an object type or intersection of object types with statically known members.,无法通过类型校验。
原有问题代码如下:
import React from 'react' import { IndexRouteProps, LayoutRouteProps, PathRouteProps, Route, useNavigate, } from 'react-router-dom' type RouteProps = PathRouteProps | LayoutRouteProps | IndexRouteProps interface ReactProps extends RouteProps { element: React.ReactNode feature?: string role?: string fallback?: React.ReactNode | string } const ProtectedRoute = ({ element, feature, role, fallback, ...restOfProps }: ReactProps) => { // TODO: check if user is in specified role const isInRole = !role // || someRoleService.isInRole(role) const featureEnabled = !feature || featureManagement.isFeatureFlagEnabled(feature) const redirectUrl = fallback as string if (!isInRole || !featureEnabled) { if (redirectUrl) { const navigate = useNavigate() navigate(redirectUrl) return null } return ( // eslint-disable-next-line react/jsx-props-no-spreading <Route {...restOfProps} element={fallback} /> ) } return ( // eslint-disable-next-line react/jsx-props-no-spreading <Route {...restOfProps} element={element} /> ) } ProtectedRoute.defaultProps = { feature: null, role: null, fallback: null, } export default ProtectedRoute
错误原因
TypeScript 语法层面不允许interface直接继承联合类型。代码中PathRouteProps | LayoutRouteProps | IndexRouteProps是三种路由属性类型组成的联合类型,成员结构不唯一,接口继承时无法确定静态已知的成员列表,因此抛出类型错误。
除此之外原代码还存在运行时问题:在组件渲染阶段直接调用navigate执行跳转,会触发React渲染过程中副作用的警告,需要放到副作用钩子中执行。
实现代码
使用交叉类型替代interface继承的写法,同时修正运行时副作用问题,完整可运行代码如下:
import React, { useEffect } from 'react' import { IndexRouteProps, LayoutRouteProps, PathRouteProps, Route, useNavigate, } from 'react-router-dom' // 用交叉类型组合原生Route联合类型和自定义属性,替代interface继承 type ProtectedRouteProps = (PathRouteProps | LayoutRouteProps | IndexRouteProps) & { element: React.ReactNode feature?: string | null role?: string | null fallback?: React.ReactNode | string | null } const ProtectedRoute = ({ element, feature = null, role = null, fallback = null, ...restOfProps }: ProtectedRouteProps) => { const navigate = useNavigate() // 权限判断逻辑 const isInRole = !role // 替换为实际角色校验逻辑,如 someRoleService.isInRole(role) const featureEnabled = !feature || featureManagement.isFeatureFlagEnabled(feature) const hasAccess = isInRole && featureEnabled const isRedirect = typeof fallback === 'string' // 跳转逻辑放到useEffect中执行,避免渲染阶段副作用 useEffect(() => { if (!hasAccess && isRedirect) { navigate(fallback, { replace: true }) } }, [hasAccess, isRedirect, fallback, navigate]) if (!hasAccess) { // 跳转场景返回null即可,useEffect中会执行重定向 if (isRedirect) return null // 回退为组件场景,渲染回退内容 return <Route {...restOfProps} element={fallback} /> } // 权限通过,渲染目标路由 return <Route {...restOfProps} element={element} /> } export default ProtectedRoute
类型说明
- 交叉类型会把联合类型的所有可能成员和自定义属性做合并,既保留了原生Route三种属性类型的校验能力,又能识别自定义属性
- 透传
...restOfProps时TypeScript会自动根据传入的路由属性(path/index/layout相关)做类型匹配,不会出现属性丢失或类型不匹配问题 - 直接在函数参数中设置默认值,替代旧版
defaultProps写法,类型推断更准确,也不需要额外写类型声明
内容的提问来源于stack exchange,提问作者adam0101
相关产品推荐
相关产品推荐

