基于同类型内部属性推导组件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
相关产品推荐
相关产品推荐

