To start with: I'm a total Typescript noob so please bear with me.
I've made a small use-case(At the bottom of this post) in which I have an array which can contain both "Class One" and "Class Two". I want to loop through that array with a forEach
and act when the property drawer
is present.
The problem that Typescript gives me in my forEach loop(Where I try to log drawer
) is:
Property 'drawer' does not exist on type 'One | Two'.
Property 'drawer' does not exist on type 'Two'.
Whilst it is clearly present in One
.
When I set my arrayWithBothOneAndTwo
to let arrayWithBothOneAndTwo = [] as One[]
it won't give me the problem of drawer not existing in my forEach loop but since it does contain Two I don't think thats a proper solution and will throw other errors.
Changing my forEach loop to
arrayWithBothOneAndTwo.forEach((item: One) => {
console.log(item.drawer)
})
Gives me the error:
Argument of type '(item: One) => void' is not assignable to parameter of type '(value: One | Two, index: number, array: (One | Two)[]) => void'.
Types of parameters 'item' and 'value' are incompatible.
Type 'One | Two' is not assignable to type 'One'.
Property 'drawer' is missing in type 'Two' but required in type 'One'.
So in Short:
How can I use item.drawer
in my forEach without throwing errors in my code?
class One {
drawer: string[] = []
constructor () {
this.drawer.push('A')
this.drawer.push('B')
this.drawer.push('C')
}
}
class Two {
notADrawer: string[] = []
constructor () {
this.notADrawer.push('D')
this.notADrawer.push('E')
this.notADrawer.push('F')
}
}
// make an array with some one classes
const someOnes = [new One(), new One()]
// make an array with some two classes
const someTwos = [new Two(), new Two(), new Two(), new Two()]
// init an array which can contain both One or Two
let arrayWithBothOneAndTwo = [] as Array<One | Two>
// populate array with some One classes and some Two classes
arrayWithBothOneAndTwo = [...someOnes, ...someTwos]
// Loop through it and show the drawer array (if present)
arrayWithBothOneAndTwo.forEach((item) => {
console.log(item.drawer)
})
CodePudding user response:
To get TypeScript to correctly narrow the type, you have to add a check to see whether the property is present:
arrayWithBothOneAndTwo.forEach((item) => {
if('drawer' in item) {
console.log(item.drawer); // type of item now narrowed to One
}
});
If you want, you can make that more explicit with a type predicate in a user-defined type guard:
arrayWithBothOneAndTwo.forEach((item) => {
if(isOne(item)) {
console.log(item.drawer);
}
});
function isOne(item: One | Two): item is One {
return 'drawer' in item;
}