Reputation: 3290
This class holds either a single Item
or an array of items Item[]
, determined by a setting at runtime (this.config.isMultiple
).
How can I narrow down the class' generic type with a type guard to Item[]
in the whole if (this.isMultiple)
-Block? (which has many, many more accesses than this example, each needing to have an individual typecast, otherwise).
Minimum working example:
interface Item { /* ... */ }
class Demo <DATATYPE extends Item | Item[]> {
config: { isMultiple: boolean; }
value: DATATYPE;
get isMultiple(): boolean {
return this.config.isMultiple;
}
addValue(value: Item) {
if (this.isMultiple) {
if (!Array.isArray(this.value)) {
this.value = [] as Item[];
// TS2322: Type 'Item[]' is not assignable to type 'DATATYPE'. 'Item[]' is assignable to the constraint of type 'DATATYPE', but 'DATATYPE' could be instantiated with a different subtype of constraint 'Item | Item[]'.
}
this.value.push(value);
// TS2339: Property 'push' does not exist on type 'Item | Item[]'. Property 'push' does not exist on type 'Item'.
} else {
this.value = value;
// TS2322: Type 'Item' is not assignable to type 'DATATYPE'. 'Item' is assignable to the constraint of type 'DATATYPE', but 'DATATYPE' could be instantiated with a different subtype of constraint 'Item | Item[]'.
}
}
}
Upvotes: 1
Views: 197
Reputation: 415
Provide this
type to the addValue function in the class.
interface MultipleDemoThis<T> {
config: { isMultiple: true; }
value: T[];
get isMultiple(): true;
}
interface SingleDemoThis<T> {
config: { isMultiple: false; }
value: T;
get isMultiple(): false;
}
type DemoThis<T> = MultipleDemoThis<T> | SingleDemoThis<T>
interface Item { /* ... */ }
class Demo<T extends Item> {
config = { isMultiple: true }
get isMultiple(): boolean {
return this.config.isMultiple;
}
addValue(this: DemoThis<T>, value: T) {
if (this.isMultiple) {
if (!Array.isArray(this.value)) {
this.value = [];
}
this.value.push(value);
} else {
this.value = value;
}
}
}
class Demo <DATATYPE extends Item> {
config: { isMultiple: boolean }
value?: DATATYPE
values?: DATATYPE[]
get isMultiple(): boolean {
return this.config.isMultiple
}
// Please use a getter or a function to access the data.
// this is an example
getValue() {
if(this.isMultiple) {
return this.values
}
return this.value
}
addValue(value: DATATYPE) {
if (this.isMultiple) {
if (!Array.isArray(this.values)) {
this.values = []
}
this.values.push(value)
} else {
this.value = value
}
}
}
💡 You may need to clean this.values
after switching isMultiple
.
as
keywordclass Demo <DATATYPE extends Item | Item[]> {
config: { isMultiple: boolean; }
value: DATATYPE;
get isMultiple(): boolean {
return this.config.isMultiple;
}
addValue(value: Item) {
if (this.isMultiple) {
if (!Array.isArray(this.value)) {
this.value = <Item>[] as DATATYPE
}
// we can be sure it is an array, so we use `as` to tell TS it must be an Array.
(this.value as Item[]).push(value)
} else {
this.value = value as DATATYPE
}
}
}
Upvotes: 1