Home > Net >  Implicit interface based on property value
Implicit interface based on property value

Time:08-30

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)?

CodePudding user response:

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`.
  }
}

CodePudding user response:

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

CodePudding user response:

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;
  }
}
  • Related