Typescript - 如何基于带有默认值的可选 bool 参数执行条件返回类型

标签 typescript default-parameters conditional-types

我正在将一些现有的 JS 代码转换为 TS,我们使用了一种模式,我不知道如何用 typescript 正确表达:

function getVehicles({
    brandFields = false,
    ownerFields = false,
    maintenanceFields = false
} = {}) {
    // building and executing some SQL
}

我们的存储库严重依赖这种模式,我们将获取成本高昂的数据放在一个标志后面,并且一个函数可以拥有多个这样的标志。

现在,尝试输入返回值的不同部分是一项工作,但效果很好:

type Vehicle = { id: dbId, manufactureDate: Date, color: string }
type VehicleBrand = { brandName: string, brandCountry: string }
type VehicleOwner = { owner: Person }
type VehicleMaintenance = { maintenance: { date: Date, place: string, operation: string } [} }

function getVehicles({
    brandFields = false,
    ownerFields = false,
    maintenanceFields = false
} = {}): (Vehicle & VehicleBrand & VehicleOwner & VehicleMaintenance) [] {
    // building and executing some SQL
}

但我想让返回类型更精确。 This SO question建议进行重载,但由于排列的数量,在这种情况下这是不切实际的。

所以我认为留给我的唯一选择是使用泛型和条件类型,大致如下:

// With only one parameter for simplicity
function getVehicles<
    Brand extends boolean
>({
    brandFields: Brand = false
} = {}): (
    Vehicle &
    (Brand extends true ? VehicleBrand : {})
) [] {
    // building and executing some SQL
}

但我还没有找到一种方法,让 typescript 满意,同时在所有情况下返回尽可能窄的类型。

getVehicles()                         // should return Vehicle
getVehicles({ brandFields: false })   // should return Vehicle
getVehicles({ brandFields: true })    // should return Vehicle & VehicleBrand
getVehicles({ brandFields: boolean }) // should return Vehicle & (VehicleBrand | {})

我最接近的是这个签名,但它太宽松了:

function getVehicles<
    Brand extends boolean
>({
    brandFields: Brand | false = false // <-- union to avoid an error ...
} = {}): (
    Vehicle &
    (Brand extends true ? VehicleBrand : {})
) [] {
    // building and executing some SQL
}

getVehicles({ brandFields: true }) // but returns Vehicle & (VehicleBrand | {}) in this case

在当前 typescript 的限制下这是否可以实现?

最佳答案

您可以通过conditional types来实现这一点,像这样:

type Vehicle<O extends OptionsFlags> = VehicleBase &
  (O extends { brandFields: true }
    ? VehicleBrand
    : O extends { brandFields: false | undefined }
    ? {}
    : VehicleBrand | {}) &
  (O extends { ownerFields: true }
    ? VehicleOwner
    : O extends { ownerFields: false | undefined }
    ? {}
    : VehicleOwner | {}) &
  (O extends { maintenanceFields: true }
    ? VehicleMaintenance
    : O extends { maintenanceFields: false | undefined }
    ? {}
    : VehicleMaintenance | {});

interface OptionsFlags {
  brandFields?: boolean;
  ownerFields?: boolean;
  maintenanceFields?: boolean;
}

interface VehicleBase {
  id: dbId;
  manufactureDate: Date;
  color: string;
}
interface VehicleBrand {
  brandName: string;
  brandCountry: string;
}
interface VehicleOwner {
  owner: Person;
}
interface VehicleMaintenance {
  maintenance: { date: Date; place: string; operation: string }[];
}

function getVehicles<O extends OptionsFlags>({
  brandFields = false,
  ownerFields = false,
  maintenanceFields = false,
}: O = {} as O): Vehicle<O>[] {
  // ...
}

getVehicles({ brandFields: true }) // return type is Vehicle<{ brandFields: true }>[]

但是...

根据您如何使用它,您可能会发现定义 Vehicle 更有帮助。像这样输入,允许每个 Vehicle子类型解析为可能具有可选属性的单个接口(interface):

type Vehicle<O extends OptionsFlags> = VehicleBase &
  (O extends { brandFields: true }
    ? VehicleBrand
    : O extends { brandFields: false | undefined }
    ? {}
    : Partial<VehicleBrand>) &
  (O extends { ownerFields: true }
    ? VehicleOwner
    : O extends { ownerFields: false | undefined }
    ? {}
    : Partial<VehicleOwner>) &
  (O extends { maintenanceFields: true }
    ? VehicleMaintenance
    : O extends { maintenanceFields: false | undefined }
    ? {}
    : Partial<VehicleMaintenance>);

Vehicle<{ brandFields: boolean }>那么就相当于:

{
  id: dbId;
  manufactureDate: Date;
  color: string;
  brandName?: string;
  brandCountry?: string;
}

关于Typescript - 如何基于带有默认值的可选 bool 参数执行条件返回类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68181933/

相关文章:

typescript 条件类型缺少属性

TypeScript 元组条件比较始终计算为 false

c++ - 当涉及参数包时,我可以依赖具有默认值的参数类型被推断为空包这一事实吗?

Kotlin 默认参数仅在最后起作用

c# - 允许将私有(private)常量用作公共(public)方法的默认参数背后的想法是什么?

typescript - 检查接口(interface)是否有必填字段

json - 如何防止我的对象的某些字段被类转换器转换

angular - 当输入值不变时如何强制对输入进行变化检测

typescript - 也许是 typescript 中的一种类型

TypeScript:在具有类型安全性的运行时向类添加动态字段