TypeScript中如何基于带默认值的可选布尔参数实现条件返回类型
Absolutely, you can achieve this precise type narrowing in TypeScript using generics with conditional types and proper type inference. Here's a clean implementation that handles all your desired scenarios:
type dbId = string | number; // Assuming this is your ID type type Person = { name: string; id: dbId }; // Defining Person since it's referenced in VehicleOwner // Your existing core type definitions 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 }[] }; // Generic interface to type the function's options with boolean literals type GetVehiclesOptions< Brand extends boolean = false, Owner extends boolean = false, Maintenance extends boolean = false > = { brandFields?: Brand; ownerFields?: Owner; maintenanceFields?: Maintenance; }; function getVehicles< Brand extends boolean = false, Owner extends boolean = false, Maintenance extends boolean = false >( { brandFields, ownerFields, maintenanceFields }: GetVehiclesOptions<Brand, Owner, Maintenance> = {} as GetVehiclesOptions<Brand, Owner, Maintenance> ): ( Vehicle & (Brand extends true ? VehicleBrand : {}) & (Owner extends true ? VehicleOwner : {}) & (Maintenance extends true ? VehicleMaintenance : {}) )[] { // Your SQL construction and execution logic here return [] as any; // Placeholder for your actual implementation }
How this works:
- Generic Parameter Tracking: We use three constrained generics (
Brand,Owner,Maintenance) with default values offalse. TypeScript will infer the exact boolean literal (true/false) when you pass a literal value, or the broaderbooleantype if you pass a variable of typeboolean. - Conditional Return Type: For each flag, we use a conditional type to either include the related fields (if the flag is
true) or an empty object (which has no effect on the type intersection) if it'sfalseor unspecified. - Default Parameter Handling: Casting the empty default object to
GetVehiclesOptions<Brand, Owner, Maintenance>helps TypeScript correctly infer the defaultfalsevalues when no options are provided.
Validating all your scenarios:
No arguments passed:
const basicVehicles = getVehicles(); // Type: Vehicle[] ✅Explicit
falsefor a flag:const noBrandVehicles = getVehicles({ brandFields: false }); // Type: Vehicle[] ✅Explicit
truefor a flag:const brandedVehicles = getVehicles({ brandFields: true }); // Type: (Vehicle & VehicleBrand)[] ✅Multiple
trueflags:const fullDetailVehicles = getVehicles({ brandFields: true, ownerFields: true, maintenanceFields: true }); // Type: (Vehicle & VehicleBrand & VehicleOwner & VehicleMaintenance)[] ✅Passing a dynamic
booleanvariable:const shouldIncludeBrand: boolean = Math.random() > 0.5; const dynamicVehicles = getVehicles({ brandFields: shouldIncludeBrand }); // Type: (Vehicle & (VehicleBrand | {}))[] (equivalent to Vehicle[] | (Vehicle & VehicleBrand)[]) ✅
Quick notes:
- The empty object
{}in intersections is safe becauseVehicle & {}simplifies directly toVehicle— it doesn’t add or remove any properties. - If you prefer the dynamic case to show
(Vehicle & Partial<VehicleBrand>)[]instead of the union, you can swap{}withPartial<VehicleBrand>in the conditional types, but both representations are functionally equivalent.
内容的提问来源于stack exchange,提问作者VincentGuinaudeau
相关产品推荐
相关产品推荐

