简体   繁体   English

缩小字段类型,基于另一个字段

[英]Narrow down type of field, based on another field

I have this interface:我有这个界面:

enum CatEngine {
  Electric = "ev",
  Petrol = "petrol",
}

interface ElectricEngineDetails {
  range: number
}


interface PetrolEngineDetails {
  mpg: number
}

interface ICar {
  engine: CarEngine;
  details: ElectricEngineDetails | PetrolEngineDetails;
}

Is there a way to write a type guard that accepts an engine and a details , and returns the details , narrowed down to the corresponding engine type?有没有办法编写一个接受enginedetails的类型保护,并返回details ,缩小到相应的engine类型? I know it can be done in-place but I wish to extract it to a separate function.我知道它可以就地完成,但我希望将其提取到单独的 function 中。

You could use a type instead of an interface for ICar (or Car since it's not an interface anymore):您可以使用type而不是ICarinterface (或Car ,因为它不再是接口):

type Car = { engine: CarEngine.Electric; details: ElectricEngineDetails } | { engine: CarEngine.Petrol; details: PetrolEngineDetails };

or you could create interfaces for the types:或者您可以为以下类型创建接口:

type Car = IElectricCar | IPetrolCar;

interface IElectricCar {
  engine: CarEngine.Electric;
  details: ElectricEngineDetails;
}

interface IPetrolCar {
  engine: CarEngine.Petrol;
  details: PetrolEngineDetails;
}

You can declare a generic interface, something like this:您可以声明一个通用接口,如下所示:

type ICar<T extends CarEngine> = {
  engine: T;
  details: T extends CarEngine.Electric ? ElectricEngineDetails : PetrolEngineDetails;
}

This will compute the type for details depending on the type of engine .这将根据engine的类型计算details的类型。 You can use it like this:你可以像这样使用它:

const ev: ICar<CarEngine.Electric> = {
 engine: CarEngine.Electric,
 details: {
  range: 23,
 }
}

Trying to use mpg will result in an error, as TS knows that details are of type ElectricEngineDetails .尝试使用mpg会导致错误,因为 TS 知道details的类型是ElectricEngineDetails

With this you can write up a type guard function that does the type narrowing:有了这个,你可以编写一个类型保护 function 来缩小类型:

const isElectric = (car: ICar<CarEngine.Electric> | ICar<CarEngine.Petrol>): car is ICar<CarEngine.Electric> => {
  if (car.engine === CarEngine.Electric) {
    return true;
  }

  return false;
}

Now you can use it like this:现在你可以像这样使用它:

function something (car: ICar<CarEngine.Petrol> | ICar<CarEngine.Electric>): void {
   if (isElectric(car)) {
     car.details.range;
   } else {
     car.details.mpg;
   }
}

Here's a full example on playground .这是Playground上的完整示例。

What you are looking for is a discriminated union你正在寻找的是一个有歧视的工会

Basically, you need to define an union that is composed of all the possibilities, but all your interfaces must have a common and discriminant property that will allow Typescript to know how to eliminate types conditionally基本上,您需要定义一个由所有可能性组成的联合,您的所有接口都必须具有一个共同的判别属性,这将允许 Typescript 知道如何有条件地消除类型

You can test it here你可以在这里测试

enum CarEngine {
  Electric = "ev",
  Petrol = "petrol",
}

interface ElectricEngineDetails {
  range: number
}


interface PetrolEngineDetails {
  mpg: number
}

// Discriminant union, engine will be the discriminant property
type Car = IElectricCar | IPetrolCar;

interface IElectricCar {
  // Electric car will have 'ev' as a type for engine
  engine: CarEngine.Electric;
  details: ElectricEngineDetails;
}

interface IPetrolCar {
  // Petrol car will have 'petrol' as a type for engine
  engine: CarEngine.Petrol;
  details: PetrolEngineDetails;
}

declare const car: Car

// car types are discriminated based on the `engine` property
if (car.engine === CarEngine.Electric) {
  car.details.range // ok
  car.details.mpg // Doesn't exist
} else {
  // Removed IElectricCar from the union because Typescript know car.engine can
  // not be CarEngine.Electric anymore, so only IPetrolCar is available in the union
  car.details.mpg // ok
  car.details.range // Doesn't exist
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM