I want to create function that accepts obj
and value
as arguments and runs objects method add
with passed value. Unfortunetly obeject can be either typeof A
or B
. Depending on object type method add
accepts string
or number
. How can i achive that?
class A {
add(a: number) {
return 10 a
}
}
class B {
add(a: string) {
return 'ten' a
}
}
I tried doing something like that but typescript lints it (and I understand that).
const runObejctMethod = (obj: A | B, value: string | number) =\> {
obj.add(value)
}
error:
TS2345: Argument of type 'string | number' is not assignable to parameter of type 'never'. Type 'string' is not assignable to type 'never'
CodePudding user response:
To create a function that can accept objects of different types and run their add method with a passed value, you can use type guards to check the type of the object and then call the appropriate method.
Here is an example of how you can implement this function:
class A {
add(a: number) {
return 10 a;
}
}
class B {
add(a: string) {
return "ten" a;
}
}
function runAdd(obj: A | B, value: any) {
if (obj instanceof A || ) {
return obj.add(value);
} else if (obj instanceof B) {
return obj.add(value);
}
}
const a = new A();
const b = new B();
console.log(runAdd(a, 5)); // Output: 15
console.log(runAdd(b, "hello")); // Output: "tenhello"
In this example, the runAdd function accepts an object obj of type A or B and a value value of any type. It then uses a type guard to check the type of obj and calls the appropriate add method with the passed value. You can then use this function to call the add method of an object of type A or B with any type of value.
CodePudding user response:
You can try using type guards to determine the type of the object and then call the correct method. You can do this by checking the type of the object with the typeof
operator, or by checking for the existence of a specific property on the object.
Here's an example using the typeof
operator:
const runObejctMethod = (obj: A | B, value: string | number) => {
if (typeof obj === 'A') {
obj.add(value as number);
} else {
obj.add(value as string);
}
}
In this example, we use the typeof
operator to check the type of obj
. If it's an instance of A
, we call the add
method with a value that has been explicitly typed as a number using the as
keyword. Otherwise, we call the add
method with a value that has been explicitly typed as a string.
You can also use a type guard by checking for the existence of a specific property on the object. For example, you could add a property called isA
to the A
class and use it in a type guard like this:
class A {
isA = true;
add(a: number) {
return 10 a
}
}
class B {
add(a: string) {
return 'ten' a
}
}
const runObejctMethod = (obj: A | B, value: string | number) => {
if (obj.isA) {
obj.add(value as number);
} else {
obj.add(value as string);
}
}
In this example, we check for the existence of the isA
property on the obj
object. If it exists, we know that obj
is an instance of A
, and we call the add
method with a value that has been explicitly typed as a number
. If the isA
property doesn't exist, we know that obj
is an instance of B
, and we call the add method with a value that has been explicitly typed as a string.
CodePudding user response:
You may use generics
const runObejctMethod2 = <C extends {add(v: any): any}>(obj: C, value: Parameters<C['add']>[0]) => {
obj.add(value)
}
There are other methods (like overloaning), but they may require casting with as
or long generics
CodePudding user response:
You can use a generically constrained function signature to accept an object whose add
method is compatible with the constraint.
You can also define the compatibility of the constraint from specific classes that you choose by using a type utility.
I've explained this more in the comments in the code below:
class A {
add (value: number): number {
return 10 value;
}
}
class B {
add (value: string): string {
return 'ten' value;
}
}
class C {
add (value: { num: number }): { num: number } {
return { num: 10 value.num };
}
}
function runObejctMethod <T extends Parameters<(A | B)['add']>[0]>(
obj: { add: (value: T) => T },
value: T,
): void {
const result = obj.add(value);
console.log(result);
}
// The constraint expression above "Parameters<(A | B)['add']>[0]" means:
// "a union of the type of the first parameter of A.add or B.add",
// so — in this case — it means "string | number":
type Constraint = Parameters<(A | B)['add']>[0];
//^? type Constraint = string | number
// You could add more compatible classes to the union
// to extend the accepted types in the constraint,
// for example: Parameters<(A | B | C | D)['add']>[0]
// --------------------
// Usage:
const a = new A();
const b = new B();
const c = new C();
// Ok:
runObejctMethod(a, 1); //=> Logs: 11
runObejctMethod(b, ' thousand'); //=> Logs: "ten thousand"
// Not ok (as expected):
runObejctMethod(a, ' thousand'); /*
~~~~~~~~~~~
Argument of type 'string' is not assignable to parameter of type 'number'.(2345) */
runObejctMethod(b, 1); /*
~
Argument of type 'number' is not assignable to parameter of type 'string'.(2345) */
runObejctMethod(c, { num: 1 }); /*
~
Argument of type 'C' is not assignable to parameter of type '{ add: (value: string | number) => string | number; }'.
Types of property 'add' are incompatible.
Type '(value: { num: number; }) => number' is not assignable to type '(value: string | number) => string | number'.
Types of parameters 'value' and 'value' are incompatible.
Type 'string | number' is not assignable to type '{ num: number; }'.
Type 'string' is not assignable to type '{ num: number; }'.(2345) */
c
is not a compatible object type, but if you had included it in the original constraint, it would be allowed:
class C {
add (value: { num: number }): { num: number } {
return { num: 10 value.num };
}
}
function runObejctMethod <T extends Parameters<(A | B | C)['add']>[0]>(
// ^
// C is now included in the union
obj: { add: (value: T) => T },
value: T,
): void {
const result = obj.add(value);
console.log(result);
}
const c = new C();
runObejctMethod(c, { num: 1 }); // Ok