繁体   English   中英

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

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

我有这个界面:

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

interface ElectricEngineDetails {
  range: number
}


interface PetrolEngineDetails {
  mpg: number
}

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

有没有办法编写一个接受enginedetails的类型保护,并返回details ,缩小到相应的engine类型? 我知道它可以就地完成,但我希望将其提取到单独的 function 中。

您可以使用type而不是ICarinterface (或Car ,因为它不再是接口):

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

或者您可以为以下类型创建接口:

type Car = IElectricCar | IPetrolCar;

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

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

您可以声明一个通用接口,如下所示:

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

这将根据engine的类型计算details的类型。 你可以像这样使用它:

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

尝试使用mpg会导致错误,因为 TS 知道details的类型是ElectricEngineDetails

有了这个,你可以编写一个类型保护 function 来缩小类型:

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

  return false;
}

现在你可以像这样使用它:

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

这是Playground上的完整示例。

你正在寻找的是一个有歧视的工会

基本上,您需要定义一个由所有可能性组成的联合,您的所有接口都必须具有一个共同的判别属性,这将允许 Typescript 知道如何有条件地消除类型

你可以在这里测试

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