简体   繁体   中英

Method argument and return types override in typescript

I wan't to describe an abstract method in abstract class which can take number or string and also return number or string; I'm using | symbol to tell method that it's arguments and return types may vary from string to number.
Then I'm creating two classes b and c which are extended from abstract class a and trying to override method test() with no argument and return type variation.
Next, I'm declaring variable x which type could be similar to b or c class, and I'm creating instace of one of those classes depending on random statement.
And finally I'm trying to call test() method, but TS compiler giving me error described below.

abstract class a {
    abstract test(x: string | number): string | number;
}

class b extends a {
    test(x: number): number {
        return x;
    }
}


class c extends a {
    test(x: string): string {
        return x;
    }
}

let x: b | c;


if (Math.random() > 0.5) {
    x = new b()
} else {
    x = new c()
};

x.test(1)

Here is error from TS compiler:

Cannot invoke an expression whose type lacks a call signature. Type '((x: number) => number) | ((x: string) => string)' has no compatible call signatures. (property) test: ((x: number) => number) | ((x: string) => string)

Perhaps I'm using wrong aproach or I misunderstood TS documentation, if so - could you please point me out better way of my goal.
Sorry for poor class names and absence of any "fiddle" - I coudn't find any js playground websites which highlights TS compiler error, so I recommend official TS Playground

When you create an instance of the class in a if block then the typescript compiler is unable to figure out which type x will be. That is OK, but the problem is that you then try to call the test function with a number and that is only possible when the type is b. Since the compiler think there is a possibility that x is of type c, you get an error.

You need to assure the compiler that when you call test then the function you call will always match the parameters provided.

You can either:

  1. Change the call signatures so that both accept any type, this way it does not matter to the compiler which of the methods are called:

     class b extends a { test(x: any) { return x; } } class c extends a { test(x : any) { return x; } } 
  2. Call the method within the if block:

     if (Math.random() > 0.5) { x = new b(); x.test(1); } else { x = new c(); x.test('1'); } 
  3. Typeguard you method call:

     if (x instanceof b) x.test(1); else if(x instanceof c) x.test('1'); 

Check out the handbook on Union types and typeguards: https://www.typescriptlang.org/docs/handbook/advanced-types.html#union-types .

EDIT: The suggestion for you so that you dont have to typeguard you type on every call, would be to have the type checking done in the methods themselves. The downside of this is that will be able to call the method with an incorrect parameter without getting a warning from the compiler. Here is an example of how that could look:

abstract class a {
    protected abstract internal(x: any): any;

    public test(x: string | number): string | number {
        return this.internal(x);
    }
}

class b extends a {
    protected internal(x) {
        if (typeof x === "number") 
            return x;
        else
            throw new Error("Invalid argument");
    }
}


class c extends a {
    protected internal(x) {
        if (typeof x === "string")
            return x;
        else
            throw new Error("Invalid argument");
    }
}

let x: b | c;


if (Math.random() > 0.5) {
    x = new b();
} else {
    x = new c();
}

x.test(1);

You are declaring x as either b or c and then try to redefine it by making it only b or only c (as I understand it but my understanding is limited) According to the editor you provided the below code works though:

abstract class a {
abstract test(input: number | string )   
}

class b extends a {
    test(x: string) {
        return x;
    }
}

class c extends a {
    test(x : number) {
        return x;
    }  
}

let x//: b | c;

if (Math.random() > 0.5) {
    x = new b()
} else {
    x = new c()
}

x.test(1)

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