Typescript:如何将枚举类型Map到相应的重叠数据

gwbalxhn  于 2023-05-01  发布在  TypeScript
关注(0)|答案(2)|浏览(90)

我试图创建一个类型,将不同的枚举类型Map到预期的数据类型,并希望确保我能够通过类型推断找出正确的数据类型。

enum Car {
    BMW,
    Toyota,
    Fiat
}

type CarWithData =
    | {
            type: Car.BMW;
            data: {
                doucheFactor: number;
            };
      }
    | {
            type: Car.Toyota;
            data: {
                dadFactor: number;
            };
      }
    | {
            type: Car.Fiat;
            data: {
                dadFactor: number;
            };
      };

function handleCar(car: CarWithData) {
    switch (car.type) {
        case Car.BMW:
            console.log(`BMW owner found with douche factor of: ${car.data.doucheFactor}`);
            console.log(`DadFactor: ${car.data.dadFactor}`); // TypeError as expected / intended;
            break;
        default:
            console.log(`Toyota / Fiat owner found with dad factor of: ${car.data.dadFactor}`);
    }
}

function getDadFactor(car: CarWithData): number {
    return car.data.dadFactor ?? -1; // Property "dadFactor" does not exist on type CarWithData
}

handleCar({ type: Car.BMW, data: { doucheFactor: 900 } });
getDadFactor({ type: Car.Fiat, data: { dadFactor: 10} });

这个例子说明了我想要完成的事情。它与handleCar中的switch语句中的类型推断一样工作。但是,getDadFactor函数抛出TypeError,因为“dadFactor”在类型CarWithData上不存在。我该怎么办?我考虑过用never声明的所有可能字段的类型做一个联合,但这似乎不是一个好的解决方案。

jogvjijk

jogvjijk1#

最明显的解决方案是先检查car.type

function getDadFactor(car: CarWithData): number {
  return car.type === Car.Toyota ? car.data.dadFactor : -1;
}

但是,如果这不适合你的情况,可能是因为你有太多的情况下,你可以添加自定义类型后卫Assert汽车有dadFactor通过执行以下操作:

const hasDadFactor = (
  arg: CarWithData,
): arg is CarWithData & { data: DadFactorData } => {
  return typeof (arg.data as DadFactorData).dadFactor === 'number';
};

function getDadFactor2(car: CarWithData): number {
  return hasDadFactor(car) ? car.data.dadFactor : -1;
}

第三个选项是创建一个只有dadFactor的汽车的类型。

type CarsWithDadFactor<T extends CarWithData = CarWithData> = T extends T
  ? 'dadFactor' extends keyof T['data']
    ? T
    : never
  : never;
function getDadFactor3(car: CarsWithDadFactor): number {
  return car.data.dadFactor;
}

第三个选项中的技巧是用T extends T完成的,您可以将其解释为联合中的for循环。
playground
作为第三个选项的更通用的版本,我们可以使用另一个通用参数来指示我们正在寻找的data的类型:

type CarsWithSpecificData<D, T extends CarWithData = CarWithData> = T extends T
  ? T['data'] extends D
    ? T
    : never
  : never;

function getDadFactor3(car: CarsWithSpecificData<{dadFactor: number}>): number {
  return car.data.dadFactor;
}

playground

zpgglvta

zpgglvta2#

一个可能的解决方案是这样的:

type ExtractData<T> = T extends { data: infer U } ? U : never;

type AllDataTypes = ExtractData<CarWithData>;

type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void
    ? I
    : never;

type CarWithPartialData = {
    type: Car;
    data: Partial<UnionToIntersection<AllDataTypes>>;
};

function handleCar(car: CarWithData) {
    switch (car.type) {
        case Car.BMW:
            console.log(`BMW owner found with douche factor of: ${car.data.doucheFactor}`);
            break;
        default:
            console.log(`Toyota / Fiat owner found with dad factor of: ${car.data.dadFactor}`);
    }
}

function getDadFactor(car: CarWithData): number {
    return (car as CarWithPartialData).data.dadFactor ?? -1;
}

在这里,我们创建一个新类型,其中的数据类型是所有可能数据类型的部分并集。这是不漂亮,难以阅读,并要求铸造,但确实有正确的功能,我正在寻找。

相关问题