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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 21:45:40