What is the Idiomatic way to write a Typescript function with casting
Problem
Please take a look at this TypeScript code:
interface PetrolEngineProperties { cylinders: number; capacity: number; }
interface ElectricEngineProperties { numberOfBatteries: number; batteryCellSize: number; }
function initializeCarEngine(engineProperties: PetrolEngineProperties | ElectricEngineProperties) { if (engineProperties instanceof ElectricEngineProperties) { // Do something with Electric Engine properties } else if (engineProperties instanceof PetrolEngineProperties) { // Do something with Petrol engine properties console.log('Petrol engine was started'); } } |
Is there a more idiomatic way to write the initializeCarEngine() API, perhaps by avoiding the isElectric boolean which seems redundant except for knowing the casting type? I attempted to simply pass in engineProperties and do type checking using instanceof and typeof, but it didn't work.
I am a beginner in the TypeScript world, so any help is appreciated. Thank you!
Solution
The problem with the approach in your question is that you're trying to control JavaScript runtime behavior based on TypeScript type information that is only available at compile time.
The JavaScript runtime just sees an engineProperties object, and is not aware of any of your TypeScript interfaces or types, which is why instanceof or typeof don't work for this case. There are a couple of approaches you could consider:
1. Use classes instead of interfaces or types
JavaScript has support for >classes, so TypeScript class information will survive compilation and be available to the JavaScript runtime. This means that you can use the >instanceof operator:
class PetrolEngineProperties { constructor( public cylinders: number, public capacity: number ) {} }
class ElectricEngineProperties { constructor( public numberOfBatteries: number, public batteryCellSize: number ) {} }
type EngineProperties = PetrolEngineProperties | ElectricEngineProperties;
function initializeCarEngine(engineProperties: EngineProperties) { if (engineProperties instanceof ElectricEngineProperties) { console.log('Electric engine was started'); } else if (engineProperties instanceof PetrolEngineProperties) { console.log('Petrol engine was started'); } }
initializeCarEngine(new ElectricEngineProperties(2, 6)); |
2. Use a discriminated union
Add a discriminant property to your types and create a >discriminated union:
interface PetrolEngineProperties { kind: 'PETROL'; cylinders: number; capacity: number; }
interface ElectricEngineProperties { kind: 'ELECTRIC'; numberOfBatteries: number; batteryCellSize: number; }
type EngineProperties = PetrolEngineProperties | ElectricEngineProperties;
function initializeCarEngine(engineProperties: EngineProperties) { if (engineProperties.kind === 'ELECTRIC') { console.log('Electric engine was started'); } else if (engineProperties.kind === 'PETROL') { console.log('Petrol engine was started'); } }
initializeCarEngine({ kind: 'PETROL', cylinders: 8, capacity: 2400 }); |
3. Write type predicates
If you find yourself in a situation where you can't or don't want to add properties to your objects, you can create >type predicates to convince the TypeScript compiler that objects are of a certain type based on their existing contents:
interface PetrolEngineProperties { cylinders: number; capacity: number; }
interface ElectricEngineProperties { numberOfBatteries: number; batteryCellSize: number; }
type EngineProperties = PetrolEngineProperties | ElectricEngineProperties;
function initializeCarEngine(engineProperties: EngineProperties) { if (isElectric(engineProperties)) { console.log('Electric engine was started'); } else if (isPetrol(engineProperties)) { console.log('Petrol engine was started'); } }
function isElectric(engineProperties: EngineProperties) : engineProperties is ElectricEngineProperties { return 'numberOfBatteries' in engineProperties && 'batteryCellSize' in engineProperties; }
function isPetrol(engineProperties: EngineProperties) : engineProperties is PetrolEngineProperties { return 'cylinders' in engineProperties && 'capacity' in engineProperties; }
initializeCarEngine({ cylinders: 8, capacity: 2400 }); |
There are other possible solutions, but the main thing to keep in mind is that in TypeScript, most of the type information disappears at compile time, and you cannot use it as a basis for runtime decisions.
Suggested blogs:
>Why Typescript does not allow a union type in an array?
>Narrow SomeType vs SomeType[]
>Create a function that convert a dynamic Json to a formula in Typescript
>How to destroy a custom object within the use Effect hook in TypeScript?
>How to type the values of an object into a spreadsheet in TypeScript?
>Type key of record in a self-referential manner in TypeScript?
>How to get the last cell with data for a given column in TypeScript?
>Ignore requests by interceptors based on request content in TypeScript?
>Create data with Typescript Sequelize model without passing in an id?
>How to delete duplicate names from Array in Typescript?