Given array of inconsistent types. This may be used to dynamically rendering of html elements for example.
interface IElement {
type: 'button' | 'input'
content: Button & Input
}
interface Button {
text?: string;
backgroundColor?: string;
}
interface Input {
value?: string;
placeholder?: string;
}
const elements: IElement[] = [
{
type: 'button',
content: {
text: 'Start',
backgroundColor: 'salmon'
}
},
{
type: 'input',
content: {
value: 'phone',
placeholder: 'Phone'
}
}
]
const newElement = elements.map(element => element.content.backgroundColor)
Is there any other ways to typecast it properly depends on type property without union?
In Typescript the standard pattern is to use what is called a "discriminated union". Basically, where in other languages you would work with IElement values, in Typescript you try to work with a union instead. This allows Typescript to determine the proper type when you check the value of the type field in a type guard.
It might look something like this:
interface Button {
type: 'button'
text: string
backgroundColor: string
}
interface Input {
type: 'input'
value: string
placeholder: string
}
type ElementUnion = Button | Input
const elements: ElementUnion[] = [
{
type: 'button',
text: 'Start',
backgroundColor: 'salmon'
},
{
type: 'input',
value: 'phone',
placeholder: 'Phone'
}
]
function doSomething (el: ElementUnion) {
if (el.type === 'button') {
console.log(el.text) // TS knows this is Button
} else if (el.type === 'input') {
console.log(el.placeholder) // TS knows this is Input
}
}
Note that I haven't defined any of the properties as optional or as an intersection, however Typescript still lets me use them in doSomething as long as I check the type field first. This is the power of a discriminated union.
And if you wish, you can still use the inheritance pattern at the same time:
type ElementType = 'button' | 'input'
interface IElement {
type: ElementType
content: unknown
}
interface Button extends IElement {
type: 'button'
content: {
text: string
backgroundColor: string
}
}
interface Input extends IElement {
type: 'input'
content: {
value: string
placeholder: string
}
}
type ElementUnion = Button | Input
function doSomething (el: ElementUnion) {
if (el.type === 'button') {
console.log(el.content.text) // TS knows this is Button
} else if (el.type === 'input') {
console.log(el.content.placeholder) // TS knows this is Input
}
}
Typescript Playground
You can use type:
type IElement = {
type: 'button'
content: Button
} | {
type: 'input'
content: Input
}
I don't know how to do it with interface.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With