简体   繁体   中英

Infer subclass property type from base class generic in typescript



    class A<T>
    {
        some: { [K in keyof T]: (x: T[K]) => T[K] }
    }

    interface IB {
        b: number
    }

    class B<T extends IB> extends A<T>
    {
        constructor()
        {
            super()

            /**
             * Type '{ b: (x: T["b"]) => number; }'
             * is not assignable to type '{ [K in keyof T]: (x: T[K]) => T[K]; }'.
             */
            this.some = {
                b: x => 2*x
            }
        }
    }

    interface IC {
        b: number
        c: boolean
    }

    class C<T extends IC> extends B<T>
    {
        constructor()
        {
            super()
            /**
             * Type '{ b: (x: T["b"]) => number; c: (x: T["c"]) => boolean; }'
             * is not assignable to type '{ [K in keyof T]: (x: T[K]) => T[K]; }'
             */
            this.some = {
                b: x => 4*x,
                c: x => !x
            }
        }
    }

Hello. I try to set generic constraint in base class "A" with the aim to automatically infer types of "some" properties in derived classes. Unfortunately I can't understand why I get TS errors like I have mentioned above. Everything seems ok from my point of view.

Thank you!

What should happen if I do this?

const b = new B<{ b: 3, z: string }>();

As you can see, I've passed in the type { b: 3, z: string } , which is acceptable because it extends { b: number } . So that means b.some.b should be of type (x: 3) => 3 . And it also means b.some.z should be of type (x: string) => string . Are either of those true of the implementation of B ? No; b.some.b is actually of type (x: 3) => number , and b.some.z is undefined. So it makes sense that the compiler is warning you.

First, let's take care of the z: string issue. Perhaps in A you want the properties of some to be optional, like this:

class A<T>
{
  some: {[K in keyof T]?: (x: T[K]) => T[K]}
}

This would allow your B and C constructor to initialize some without having to know about extra properties.

Now, about b: 3 . If you want to allow for someone to extend number , then the only safe thing you can use is the identity function:

this.some = {};
this.some.b = x => x; // okay

But probably you don't want anyone to pass in anything more specific than number for the type of b . Unfortunately there's no great way to prevent it. So, fine, just document that users should only pass in types where b can be any number . In this case you need to just tell the compiler not to worry, by asserting that this is of type B<IB> :

this.some = {};
(this as B<IB>).some.b = x => 2 * x; // okay

Similar fixes can be done for your C class. Hope that helps; good luck!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM