TFlavour is a discriminatory union, which is then a value of an Object.
Trying to implement this, works in js, but ts gets angry. ts playground link
Expected: ts to understand discriminated unions in loops, as it understands it without loops
type TFlavour = ({
natural: true,
naturalComponent : string
}) | ({
natural: false,
artificialComponent: string
})
type TIceCream = Record<string, TFlavour>
const IceCreams: TIceCream = {
CokeIceCream: {
natural:false,
artificialComponent: 'Coke'
},
Vanilla: {
natural: true,
naturalComponent: 'Vanilla Extract'
},
Mango: {
natural: true,
naturalComponent: 'Mango Fruit'
}
}
const iceCreamKeys = Object.keys(IceCreams)
iceCreamKeys.forEach( item => {
if(IceCreams[item].natural){
console.log(IceCreams[item].naturalComponent) // ts says "Property doesn't exists.."
}
})
if(IceCreams.Mango.natural){
console.log(IceCreams.Mango.naturalComponent) // Works here
}
CodePudding user response:
The problem is that the compiler doesn't know how to do narrowing on an object property like IceCreams[item]
where you are indexing with a key whose type isn't known to be a specific literal type. TypeScript is only following the type of the index, not the identity. And the type of item
is string
. If you have item1
and item2
, both of type string
, then checking IceCreams[item1]
wouldn't let you conclude anything about IceCreams[item2]
, right? And since TypeScript can't tell the difference between item1
vs item2
or item
vs item
, it can't narrow. This is a known limitation of TypeScript reported at microsoft/TypeScript#10530. Maybe someday it will be addressed. But for now, there's an easy workaround:
Just copy the value into a new variable, so that the problematic indexing occurs only once:
iceCreamKeys.forEach(item => {
const x = IceCreams[item];
if (x.natural) {
console.log(x.naturalComponent) // okay
}
})
CodePudding user response:
Instead of directly accessing the item with the index, try to store it in a separate variable. This way, TypeScript will recognize the right type:
iceCreamKeys.forEach( item => {
const c = IceCreams[item]
if(c.natural){
console.log(c.naturalComponent)
}
})
(working TS Playground)
CodePudding user response:
iceCreamKeys.forEach( item => {
if(IceCreams.item.natural){
console.log(IceCreams.item.naturalComponent) // Accessing it like this worked
}
})
Just found out, this works too.