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

在TypeScript可辨识联合中用泛型约束方法参数的实现方案

TypeScript可辨识联合中方法参数的类型收窄问题

初始实现的可辨识联合

我们定义了一个接受自身作为参数的可辨识联合:

type TextInput<Props extends Inputs<Props>> = {
    type: 'text',
    value?: string,
    options?: undefined
}

type CheckboxInput<Props extends Inputs<Props>> = {
    type: 'checkbox',
    options: string[] | number[],
    value?: Props['options'] extends Array<infer T> ? T : never
}

type Events<Props extends Inputs<Props>> = { onInput?: (arg: Inputs<Props>['value']) => any }

type Inputs<Props extends Inputs<Props>> = TextInput<Props> | CheckboxInput<Props>

当前已实现的类型约束

通过泛型函数create,我们已经能实现对value类型的约束:

type Make = <P extends Inputs<P>>(props: P) => {}

declare const create: Make

create({ type: 'text', value: '123' })  // ✅ 符合要求
create({ type: 'text', value: 123 }) // ❌ 报错(value必须为string类型)
                       ^^^^^^^^^^
create({ type: 'checkbox', options: ['a', 'b'], value: 'a' }) // ✅ 符合要求
create({ type: 'checkbox', options: [1, 2, 3], value: 2 }) // ✅ 符合要求
create({ type: 'checkbox', options: ['a', 'b'], value: 2 }) // ❌ 报错(value类型必须与options匹配)
                                                ^^^^^^^^

添加方法后遇到的问题

当我们为可辨识联合添加onInput方法,希望方法参数匹配对应value类型时,TypeScript无法正确收窄参数类型:

type TextInput<Props extends Inputs<Props>> = {
    type: 'text',
    value?: string,
    options?: undefined
} & Events<Props>

type CheckboxInput<Props extends Inputs<Props>> = {
    type: 'checkbox',
    options: string[] | number[],
    value?: Props['options'] extends Array<infer T> ? T : never
} & Events<Props>

type Events<Props extends Inputs<Props>> = { onInput?: (arg: Inputs<Props>['value']) => any }

type Inputs<Props extends Inputs<Props>> = TextInput<Props> | CheckboxInput<Props>

type Make = <P extends Inputs<P>>(props: P) => {}

declare const create: Make

create({ type: 'checkbox', options: [1, 2, 3], value: 1, onInput: (arg) => arg }) // ❌ 不符合预期:onInput的arg应仅为number类型,匹配value的类型

技术问题

是否存在方法,让可辨识联合内方法的参数使用收窄后的联合数据类型?

解决方案

方案1:移除循环引用,直接绑定事件类型到具体输入类型

避免使用循环依赖的泛型定义,将事件类型与每个输入类型的value直接绑定:

// 定义接受具体value类型的事件类型
type Events<T> = { onInput?: (arg: T) => any }

// 文本输入类型,绑定string类型的事件参数
type TextInput = {
    type: 'text',
    value?: string,
    options?: undefined
} & Events<string | undefined>

// 复选框输入类型,泛型T对应options的元素类型
type CheckboxInput<T extends string | number> = {
    type: 'checkbox',
    options: T[],
    value?: T
} & Events<T | undefined>

// 联合类型包含所有输入类型
type Inputs = TextInput | CheckboxInput<string> | CheckboxInput<number>

type Make = <P extends Inputs>(props: P) => {}

declare const create: Make

// 测试验证
create({ type: 'text', value: '123' })  // ✅
create({ type: 'text', value: 123 }) // ❌ 报错(value必须为string)
create({ type: 'checkbox', options: ['a', 'b'], value: 'a' }) // ✅
create({ type: 'checkbox', options: [1, 2, 3], value: 2 }) // ✅
create({ type: 'checkbox', options: ['a', 'b'], value: 2 }) // ❌ 报错(value类型不匹配options)
create({ type: 'checkbox', options: [1, 2, 3], value: 1, onInput: (arg) => arg }) // ✅ arg被正确推断为number | undefined

方案2:使用分布式条件类型为输入类型添加事件

通过工具类型为每个输入类型自动添加匹配其value类型的事件:

// 基础输入类型定义
type TextInput = {
    type: 'text',
    value?: string,
    options?: undefined
}

type CheckboxInput = {
    type: 'checkbox',
    options: string[] | number[],
    value?: (typeof options)[number]
}

// 工具类型:为输入类型添加匹配其value类型的onInput事件
type InputWithEvents<T> = T & {
    onInput?: (arg: T['value']) => any
}

// 最终联合类型
type Inputs = InputWithEvents<TextInput> | InputWithEvents<CheckboxInput>

type Make = <P extends Inputs>(props: P) => {}

declare const create: Make

// 测试验证
create({ type: 'checkbox', options: [1, 2, 3], value: 1, onInput: (arg) => arg }) // ✅ arg被正确推断为number | undefined

原理说明

原实现中循环引用的Inputs<Props>会导致TypeScript无法正确收窄联合类型分支的类型,Inputs<Props>['value']会被拓宽为整个联合的value类型(string | number | undefined)。通过将事件类型与具体输入类型的value直接绑定,或者使用分布式条件类型为每个分支单独添加事件,TypeScript就能正确推断方法参数的具体类型。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 10:22:04