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

如何让React高阶组件(HOC)具备类型守卫(Type Guard)能力?

解决conditionalize-component库的TypeScript类型守卫识别问题

我开发了一个名为conditionalize-component的库,用来简化React条件渲染写法:

原条件渲染写法:

{ true ? <MyComponent></MyComponent> : null }

可简化为:

<MyComponent renderIf={true} />

当前问题

TypeScript无法识别该组件的类型守卫能力,例如以下代码中,TypeScript会报错认为icon可能未定义:

<Div renderIf={!!props.icon}>
  <props.icon /> {/* <---- 类型错误,icon可能未定义! */}
</Div>

现有实现代码

核心接口定义

import type { ReactNode } from 'react'

export interface ConditionalProps {
  renderIf?: boolean
  override?: ReactNode
  fallback?: ReactNode
  excludeChildren?: boolean
}

withConditionals高阶组件工厂函数

import _ from 'lodash'
import type { FC, PropsWithoutRef, Ref } from 'react'
import React, { forwardRef } from 'react'
import type { ConditionalProps } from './types'

function withConditionals<P>(OriginalComponent: FC<P>) {
  return forwardRef((props: PropsWithoutRef<P & ConditionalProps>, ref) => {
    const conditionalKeys = ['fallback', 'override', 'renderIf', 'excludeChildren']
    const {
      fallback,
      override,
      renderIf = true,
      excludeChildren = false,
    } = _.pick(props, conditionalKeys)

    const originalProps = _.omit(props, conditionalKeys) as any

    let renderEl: any
    switch (true) {
      case excludeChildren === true:
        renderEl = originalProps?.children
        break
      case renderIf === true:
        renderEl = <OriginalComponent {...(originalProps as P)} ref={ref} />
        break
      case !!override:
        renderEl = override
        break
      default:
        renderEl = fallback
    }

    return <React.Fragment>{renderEl}</React.Fragment>
  })
}

export default withConditionals

使用示例

自定义组件使用

import { MyComponent } from '../MyComponent' 
import {} from 'conditionalize-component'
const MyComponentC = withConditionals(MyComponent)

function App(){
  return (
    <div>
      <MyComponentC renderIf={1 + 1 === 2}> See me when 1 + 1 = 2 </MyComponentC>
      <div>I always render</div>
    </div>
  )
}

原生HTML元素使用

export const Button = withConditionals((props: React.HTMLAttributes<HTMLButtonElement>) => <button {...props} />)

<Button renderIf={true}>Click me</Button>

解决方案

要让TypeScript识别组件的类型守卫能力,需要通过类型系统关联renderIf的条件与子元素的上下文类型,具体调整如下:

1. 更新类型定义

修改ConditionalProps以支持类型谓词和函数式子元素:

import type { ReactNode, PropsWithChildren } from 'react'

export type ConditionalProps<T> = {
  renderIf?: boolean | ((props: T) => boolean)
  override?: ReactNode
  fallback?: ReactNode
  excludeChildren?: boolean
}

// 定义带条件约束的组件Props类型
type ConditionalComponentProps<P> = PropsWithoutRef<P> & ConditionalProps<P> & {
  // 使用函数式子元素,让类型推断基于传入的props
  children?: (props: P) => ReactNode
}

2. 调整高阶组件实现

修改withConditionals以支持类型守卫和函数式子元素的类型推断:

import _ from 'lodash'
import type { FC, PropsWithoutRef, Ref } from 'react'
import React, { forwardRef } from 'react'
import type { ConditionalProps } from './types'

type ConditionalComponentProps<P> = PropsWithoutRef<P> & ConditionalProps<P> & {
  children?: (props: P) => ReactNode
}

function withConditionals<P>(OriginalComponent: FC<P>) {
  return forwardRef<unknown, ConditionalComponentProps<P>>((props, ref) => {
    const conditionalKeys = ['fallback', 'override', 'renderIf', 'excludeChildren', 'children']
    const {
      fallback,
      override,
      renderIf = true,
      excludeChildren = false,
      children,
    } = _.pick(props, conditionalKeys)

    const originalProps = _.omit(props, conditionalKeys) as P
    // 处理renderIf为函数的情况
    const shouldRender = typeof renderIf === 'function' ? renderIf(originalProps) : renderIf

    let renderEl: ReactNode | undefined
    switch (true) {
      case excludeChildren === true:
        renderEl = children?.(originalProps)
        break
      case shouldRender:
        // 优先使用函数式子元素,否则渲染原组件
        renderEl = children ? children(originalProps) : <OriginalComponent {...originalProps} ref={ref} />
        break
      case !!override:
        renderEl = override
        break
      default:
        renderEl = fallback
    }

    return <React.Fragment>{renderEl}</React.Fragment>
  }) as FC<ConditionalComponentProps<P>>
}

export default withConditionals

3. 使用方式调整

通过函数式子元素让TypeScript推断类型:

<Div renderIf={!!props.icon}>
  {(props) => <props.icon />} {/* TypeScript会推断props.icon已定义 */}
</Div>

如果需要更严格的类型守卫,可以让renderIf接收类型谓词函数:

<Div 
  renderIf={(props): props is { icon: React.ReactElement } => !!props.icon}
>
  {(props) => <props.icon />} {/* 类型断言直接生效 */}
</Div>

内容的提问来源于stack exchange,提问作者Islam Y-

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 21:57:16