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

基于同类型内部属性推导组件Props的TypeScript方案问询

需求说明

需要实现一个组件,其Props类型由自身的variant属性决定。需求类似可辨识联合类型,但判别器是字面量数组,且需根据数组内容返回对应类型的交集:

  • 当variant为['select']时,Props类型为SelectProps
  • 当variant为['select','drag']时,Props类型为SelectProps & DragProps

给定类型定义

type Variant = 'select' | 'drag'

// 所有功能通用的公共Props
interface CommonProps {
  variant: Variant[]
  title: string
}

// 拖拽功能专属Props
interface DragProps extends CommonProps {
  onDrag: () => void
}

// 选择功能专属Props
interface SelectProps extends CommonProps {
  onSelect: () => void
}

const ComponentWithRequiredFeatures = (props: MagicTypeProps) => {
  // 组件逻辑实现
}

期望校验逻辑

// ❌ 错误:类型不存在'onDrag'属性
<ComponentWithRequiredFeatures variant={['select']} title="Hello world" onSelect={() => {}} onDrag={() => {}} />

// ❌ 错误:缺少'onSelect'属性
<ComponentWithRequiredFeatures variant={['select', 'drag']} title="Hello world" onDrag={() => {}} />

// ✅ 合法
<ComponentWithRequiredFeatures variant={['select', 'drag']} title="Hello world" onSelect={() => {}} onDrag={() => {}} />

已实现的泛型方案

方案一:直接泛型约束

// 阻止TypeScript自动推断泛型的辅助类型
export type NoInfer<T> = T extends infer S ? S : never

type GenericExtend = CommonProps | DragProps | SelectProps

const ComponentWithRequiredFeatures = <T extends GenericExtend = CommonProps>(props: NoInfer<T>) => {
  // 组件逻辑
}

校验效果:

// ❌ 错误:类型'IntrinsicAttributes & SelectProps'不存在'onDrag'属性
<ComponentWithRequiredFeatures<SelectProps> title="Hello world" onSelect={() => {}} onDrag={() => {}} />

// ❌ 错误:类型'{ title: string; onDrag: () => void; }'缺少必填的'onSelect'属性
<ComponentWithRequiredFeatures<SelectProps & DragProps> title="Hello world" onDrag={() => {}} />

// ✅ 合法
<ComponentWithRequiredFeatures<SelectProps & DragProps> title="Hello world" onDrag={() => {}} onSelect={() => {}} />

方案二:字面量映射+联合转交集

// 将联合类型转换为交集类型的辅助类型
export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never

type LiteralPropsMapping = {
  common: CommonProps
  select: SelectProps
  drag: DragProps
}

type Literals = keyof LiteralPropsMapping // 'common' | 'drag' | 'select'

const ComponentWithRequiredFeatures = <T extends Literals = 'common'>(
  props: UnionToIntersection<LiteralPropsMapping[T]>
) => {
  // 组件逻辑
}

校验效果:

// ❌ 错误:类型'IntrinsicAttributes & SelectProps'不存在'onDrag'属性
<ComponentWithRequiredFeatures<'select'> title="Hello world" onSelect={() => {}} onDrag={() => {}} />

// ❌ 错误:类型'{ title: string; onDrag: () => void; }'缺少必填的'onSelect'属性
<ComponentWithRequiredFeatures<'select' | 'drag'> title="Hello world" onDrag={() => {}} />

// ✅ 合法
<ComponentWithRequiredFeatures<'select' | 'drag'> title="Hello world" onDrag={() => {}} onSelect={() => {}} />

待解决问题

以上两种方案都需要手动指定泛型参数,希望实现无需显式传递泛型,仅通过组件传入的variant数组自动推导对应Props类型的约束。


内容的提问来源于stack exchange,提问作者Peter

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 07:43:21