0

在 TypeScript (v4.5.4) 中,我试图通过索引签名来定义对象类型。我希望 TypeScript 强制对象中的某些子属性具有匹配的类型,但这些类型允许在顶级属性之间变化。

在下面的(非工作)示例中,我希望所有快乐的司机驾驶他们最喜欢的汽车类型。驾驶员最喜欢的汽车类型与其汽车的实际类型不匹配会导致 TypeScript 编译器错误。

type CarType = 'Minivan' | 'Sports car' | 'Sedan' ; // | 'Pickup truck' |, etc. Imagine this union type has many possible options, not just three.

type Car = {
  carType: CarType
  // A car probably has many additional properties, not just its type, but those are left out of this minimal example.
  // The solution should be resistant to adding additional properties on `Car` (and the `Driver` type below).
};

type Driver = {
  favoriteCarType: CarType
  car: Car
};

/**
 * Happy drivers drive their favorite car type.
 */
const happyDrivers: { [name: string]: Driver } = {
  alice: {
    favoriteCarType: 'Minivan',
    car: {
      carType: 'Minivan', // ✅ Alice drives her favorite type of car.
    },
  },
  bob: {
    favoriteCarType: 'Sports car',
    car: {
      carType: 'Sedan', /* ❌ Bob doesn't drive his favorite type of car!
        This currently does not throw a compiler error because my types are too permissive, but I want it to. */
    },
  },
};

我已经尝试以我能想到的所有方式将泛型应用于索引签名和/或Carand/orDriver类型,但我无法让编译器强制执行驱动程序必须与其'sfavoriteCarType完全匹配的约束。carcarType

你能帮我吗?

4

2 回答 2

1

您正在寻找的是以下工会:

type Driver = {
    favoriteCarType: "Minivan";
    car: {
        carType: "Minivan";
    };
} | {
    favoriteCarType: "Sports car";
    car: {
        carType: "Sports car";
    };
} | {
    favoriteCarType: "Sedan";
    car: {
        carType: "Sedan";
    };
}

如果您使用Car泛型(使用汽车的类型作为类型参数),您可以生成此联合,并且我们使用自定义映射类型来创建联合的每个组成部分(然后将联合索引返回到生成的映射类型):


type Car<T extends CarType = CarType> = { carType: T };


type Driver = {
  [P in CarType]: {
    favoriteCarType: P
    car: { carType: P }
  }
}[CarType];

游乐场链接

于 2022-02-14T19:22:55.513 回答
1

Titian 的回答通过使用映射类型使我走上了正确的道路。请参阅HappyDriver下面的类型。

type CarType = 'Minivan' | 'Sports car' | 'Sedan' ; // | 'Pickup truck' |, etc. Imagine this union type has many possible options, not just three.

type Car = {
  carType: CarType
  // A car probably has many additional properties, not just its type, but those are left out of this minimal example.
  // The solution should be resistant to adding additional properties on `Car` (and the `Driver` type below).
};

/**
 * A driver may drive a car of any type, not just their favorite.
 */
type Driver = {
  favoriteCarType: CarType
  car: Car
};

/**
 * A happy driver drives their favorite type of car.
 */
type HappyDriver = {
  [C in CarType]: {
    [K in keyof Driver]: Driver[K] extends Car
      ? { [K2 in keyof Car]: Car[K2] extends CarType
        ? C
        : Car[K2]
      } : Driver[K] extends CarType
        ? C
        : Driver[K] 
  }
}[CarType]

const happyDrivers: { [name: string]: HappyDriver } = {
  alice: {
    favoriteCarType: 'Minivan',
    car: {
      carType: 'Minivan', // ✅ Alice drives her favorite type of car.
    },
  },
  bob: {
    favoriteCarType: 'Sports car',
    car: {
      carType: 'Sedan', /*  Bob doesn't drive his favorite type of car, so this line causes a compiler error, which is what we want to happen. */
    },
  },
};

游乐场链接

无论如何,我已将提香的答案标记为正确答案,因为它帮助我找到了我正在寻找的答案。

于 2022-02-15T08:57:02.910 回答