从联合类型推断泛型类方法的参数

时间:2021-07-28 08:45:44

标签: typescript typescript-generics

我有这个用例,我有一个泛型类和一个包含泛型类的多个成员的容器类。我想使用 setViaFunction 来设置容器类的成员,而不取决于它们的类型。但是我收到以下错误:Argument of type 'string | boolean' is not assignable to parameter of type 'never'. Type 'string' is not assignable to type 'never'.(2345) typescript playground link

另请注意,setAnyMember 手动执行相同的操作,但并非总是可以执行此操作不会产生该错误。

还有其他方法可以在打字稿中实现此功能吗?

class GenericClass<T> {
    public value: T
    constructor(_value: T){
        this.value = _value
    }
    setValue(_value:T){
        this.value = _value
    }
}

class ContainerClass {
    sthString = new GenericClass('sdad')

    sthBoolean = new GenericClass(true)

    setAnyMember = (field: 'sthString' | 'sthBoolean', val: string | boolean) => {
    this[field].value = val
    }
    setViaFunction = (field: 'sthString' | 'sthBoolean', val: string | boolean) => {
    this[field].setValue(val)
    }
}

2 个答案:

答案 0 :(得分:0)

我猜分配没有失败,因为类型匹配

this[field].value 的类型为 string | boolean 和 val 也是。

如果您尝试调用 this[field].setValue(val) 打字稿不够聪明,无法自行找出类型:/。

我想不出一种聪明的方法来帮助打字稿自动选择正确的类型。

我会尝试通过不传递字段并通过 val 类型调用正确的方法来解决此问题


class ContainerClass {
    sthString = new GenericClass('sdad')

    sthBoolean = new GenericClass(true)

    setAnyMember = (field: 'sthString' | 'sthBoolean', val: string | boolean) => {
    this[field].value = val
    }
    setViaFunction = (val: string | boolean) => {
        if (typeof val === "string"){
            this.sthString.setValue(val)
        } else {
            this.sthBoolean.setValue(val)
        }
    }
}

答案 1 :(得分:0)

让我们定义我们的工具类型:

class GenericClass<T> {
    public value: T
    constructor(_value: T) {
        this.value = _value
    }

    setValue(_value: T) {
        this.value = _value
    }
}

// infer generic parameter from GenericClass
type GetGeneric<T> = T extends GenericClass<infer G> ? G : never
{
    type Test = GetGeneric<GenericClass<boolean>> // boolean
}

// infer all props which are instances of GenericClass
type GetProps<T> = {
    [Prop in keyof T]: T[Prop] extends GenericClass<any> ? Prop : never
}[keyof T]
{
    type Test = GetProps<ContainerClass> // "sthString" | "sthBoolean"
}

// infer correct type of GenericClass constructor argument
type GetValues<Obj, Prop> = Prop extends GetProps<Obj> ? GetGeneric<Obj[Prop]> : never
{
    type Test = GetValues<ContainerClass, 'sthString'> // string
}

class ContainerClass {
    sthString = new GenericClass('sdad')

    sthBoolean = new GenericClass(true)
    /**
     * Field is one of "sthString" | "sthBoolean"
     */
    setAnyMember<Field extends GetProps<this>>(
        /**
         * this is a Record with Field keys and appropriate GenericClass instances
         */
        this: Record<Field, GenericClass<GetValues<this, Field>>>,
        field: Field, // "sthString" | "sthBoolean"
        val: GetValues<this, Field> // get value by Field key
    ) {
        this[field].setValue(val) // ok

    }
}
const x = new ContainerClass();
x.setAnyMember('sthBoolean', true) // ok
x.setAnyMember('sthString', 'str') // ok

x.setAnyMember('sthBoolean', 32) // expected error
x.setAnyMember('sthString', false) // expected error

Playground

我使用 this 输入 setAnyMember 来给 TS 一些线索。 我还使非法状态无法表示,您不能将无效参数传递给 setAnyMember