打字稿:映射类型,联合中的剥离字段

Typescript: mapped types, strip field from a union

我正在尝试创建一个类型映射器 NewRecord<T>,它将从 <T> 中去除 id 类型。 我就是这样做的:

type NewRecord<T> = {
  [P in Exclude<keyof T, 'id'>]: T[P]
}

但是,不幸的是,它不适用于联合类型。让我举例说明:

interface IRecord {
  id: number
}

interface IBotRecord extends IRecord {
  isBot: true
  cpuCores: 4
}

interface IHumanRecord extends IRecord {
  isBot: false
  isHungry: true
}

type ICreature = IHumanRecord | IBotRecord

type INewBotRecord = NewRecord<IBotRecord>
type INewHumanRecord = NewRecord<IHumanRecord>
type INewCreature = NewRecord<ICreature>

const newHuman:INewHumanRecord = {
  isBot: false,
  isHungry: true // works!
}

const newCreature:INewCreature = {
  isBot: false,
  isHungry: true // does not exist in type NewRecord<ICreature>
}

这是因为 keyof 遍历类型的交集,而不是联合,这是预期的行为:https://github.com/Microsoft/TypeScript/issues/12948

从联合中剥离字段的正确方法是什么?

您想为联盟的每个成员应用映射类型。幸运的是,条件类型具有这种确切的行为,它们分布在裸类型参数上。这意味着映射类型独立应用于联合的每个成员,并且所有结果都联合到最终类型中。有关更多说明,请参阅 here and

在这种情况下,条件类型中的条件可以只是extends any我们不关心条件部分我们只关心条件类型的分布行为:

type NewRecord<T> = T extends any ? {
    [P in Exclude<keyof T, 'id'>]: T[P]
} : never

type INewCreature = NewRecord<ICreature>
// The above type is equivalent to
type INewCreature = {
    isBot: false;
    isHungry: true;
} | {
    isBot: true;
    cpuCores: 4;
}
const newCreature:INewCreature = {
  isBot: false,
  isHungry: true // works fine
}