I have a two classes defined as following:
class FirstClass {
values: FirstValuesType = {} as FirstValuesType;
setValues(values: FirstValuesType): void {
this.values = { ...values };
}
}
class SecondClass {
values: SecondValuesType = {} as SecondValuesType;
setValues(values: SecondValuesType): void {
this.values = { ...values };
}
}
interface CommonValuesType {
id: number;
property1: string;
property2: string;
}
enum FirstReasonEnum {
A, B, C
}
enum SecondReasonEnum {
X, Y, Z
}
interface FirstValuesType extends CommonValuesType {
reason: FirstReasonEnum;
}
interface SecondValuesType extends CommonValuesType {
reason: SecondReasonEnum;
}
And then I defined a constant which can be one of the classes as following:
const model: FirstClass | SecondClass;
When I call setValues
like this:
model.setValues(newValues);
I get the following ts error:
Argument of type '{ id: number; property1: string; property2: string; reason: FirstReasonEnum; }' is not assignable to parameter of type 'never'.
The intersection 'FirstValuesType & SecondValuesType' was reduced to 'never' because property 'reason' has conflicting types in some constituents.(2345)
How can I solve this ?
Edit:
Here is the link to the playground:
CodePudding user response:
Your code assumes that model
is an instance of FirstClass
, but the types say that it could be an instance of FirstClass
or it could be an instance of SecondClass
. Since it's unclear which it is, but setValues
needs to know, TypeScript is highlighting for you that the argument you're passing isn't valid (or perhaps I should say might not be valid). And it can't be valid, because it can't be simultaneously both a FirstValuesType
and a SecondValuesType
(which is what FirstValuesType & SecondValuesType
means), since the types of reason
differ.
The minimum-changes way to solve it is to test what you have to see whether it's a FirstClass
instance, which you can do since you've used a class (not just an interface). So this fixes it:
if (model instanceof FirstClass) {
model.setValues(newValues);
} else {
// ...code here to do something with the `SecondClass` `model`
}
That said, you might want to reconsider the overall structure, but it really depends on the specifics and this looks like a simplified example (appropriately simplified, for the purposes of asking the question). FirstClass
and SecondClass
, and FirstValuesType
and SecondValuesType
, are identical other than the type of the enum used for reason
. You might consider using generic type parameters on a single TheClass
and ValuesType
instead. But that wouldn't change the fundamental issue that the code in init
is assuming that the model is one thing when it may be one or the other of two things.
Side note: This part of the init
code seems slightly suspicious:
const model: TheClass<EnumType> = _model;
// ...
model.setValues(newValues);
The model
constant and the _model
parameter point to the same object, so it would be more direct to not create an unnecessary additional identifier and just use _model
directly:
_model.setValues(newValues);
Either way, you're modifying the object that was passed in. (If you meant to copy the object, you'll need to do a copy operation.)