0

I currently have the following interface:

export interface Vehicle {
  id: number;
  type: 'car' | 'airplane';
  jet_engines?: number;
  wheels?: number;
}

But I also don't want cars to accept the jet_engines property, and airplanes shouldn't have the wheels property.

I want to use it like this:

const func = (vehicle: Vehicle) => {
  if (vehicle.type === 'airplane') {
    // TS should know here that `jet_engines` exists in `vehicle`.
  }
}

I want to avoid using like this:

const func = (vehicle: Car | Airplane) => {

Is this possible? Is this possible while keeping Vehicle an interface (not changing it to a type)?

2
  • No it's not. Why can't you use a type vor Vehicle? Commented Aug 29, 2022 at 12:35
  • @TobiasS. I can, but it's just a small inconvenience as I would have to rename it because I have a different naming convention for types and interfaces in my code. Commented Aug 29, 2022 at 12:47

3 Answers 3

2

Yes you can:

export type Vehicle = {
  id: number
} & VehicleType

type VehicleType = Car | Airplane

type Car = {
  type: 'car'
  wheels: number
}

type Airplane = {
  type: 'airplane'
  jet_engines: number
}

const func = (vehicle: Vehicle) => {
  if (vehicle.type === 'airplane') {
    // TS should know here that `jet_engines` exists in `vehicle`.
  }
}
Sign up to request clarification or add additional context in comments.

Comments

2

You can update Vehicle to the following:

export type Vehicle = 
| {
    id: number;
    type: 'car';
    wheels: number;
  }
| {
    id: number;
    type: 'airplane';
    jet_engines: number;
  }

This way, you can do something like:

if (vehicle.type === 'car') {
  // TS knows the vehicle has wheels
} else {
  // TS knows the vehicle has jet engines
}

And, if you have multiple common fields, to avoid duplication, you can do something like the following:

type VehicleCommonFields = {
  id: number;
  // other common fields
}

type VehicleTypes = 
| {
    type: 'car';
    wheels: number;
  }
| {
    type: 'airplane';
    jet_engines: number;
  }

export type Vehicle = VehicleCommonFields & VehicleTypes

Comments

1

You can create a VehicleType that lists all possible vehicles.

Then create a generic type that holds the common fields.

Then create interfaces for both (Car/Airplay) and in the end create a union type.

You can even create a type guard function. Something similar to this:

type VehicleType = 'car' | 'airplane';

export interface GenericVehicle <T extends VehicleType> {
  id: number;
  type: T;
}

interface Airplane extends GenericVehicle<'airplane'> {
  jet_engines: number
}

interface Car extends GenericVehicle<'car'> {
  wheels: number
}

type Vehicle = Airplane | Car

const isCar = (vehicle: Vehicle): vehicle is Car => {
  return vehicle.type === 'car';
}

function doSmth(vehicle: Vehicle) {
  if (isCar(vehicle)) {
    vehicle.wheels;
  } else {
    vehicle.jet_engines;
  }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.