如何在Typescript中覆盖交叉类型的属性?

15

假设我有以下这些类型:

type BaseAnimal = {
  species: string
  owner: boolean
}

type Cat = BaseAnimal & {
  species: 'cat'
  hasTail: boolean
}

type Dog = BaseAnimal & {
  species: 'dog'
  likesWalks: boolean
}

type Animal = Cat | Dog

我希望创建一个名为AnimalParams的类型,它与Animal相同,只是 owner属性是字符串。

我不能执行以下任何一项操作。

// This seems to keep the owner property from Animal instead of overwriting
// So it raises an error if I try to specify owner as a string
type AnimalParams = Animal & {
  owner: string
}

// This strips away properties unique to Cat or Dog
// So it raises an error if I try to specify hasTail or likesWalks
type AnimalParams = Omit<Animal, 'owner'> & {
  owner: string
}

现在,我能想到的唯一解决方法是按照以下步骤进行,但这似乎过于重复。有没有更加简洁、清晰的方法?

现在,我所能想到的唯一解决办法是按照上述方式操作,但这似乎过于重复。是否有更简便、更简明的方法?
type CatParams = Omit<Cat, 'owner'> & {
  owner: string
}

type DogParams = Omit<Dog, 'owner'> & {
  owner: string
}

type AnimalParams = CatParams | DogParams

我在 Stack Overflow 上阅读了一些关于实用类型(如 在 TypeScript 的 d.ts 文件中覆盖接口属性类型,这是针对接口的),但是没有找到我需要的内容。感谢任何提前回答!

3个回答

14

不必手动从每种类型中省略 owner 属性,您可以使用分布式条件类型

type OmitOwner<T = Animal> = T extends BaseAnimal ? Omit<T, 'owner'> : never;

type AnimalParams = OmitOwner & {
  owner: string
};

这相当于:

(Omit<Cat, 'owner'> & { owner: string; }) 
  | (Omit<Dog, 'owner'> & { owner: string; })

这是由于联合类型自动分配所致

使用类型参数A | B | C实例化 T extends U ? X : Y 中的 T,将被解析为 (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)

playground


为什么原始尝试不起作用?

keyof联合产生类型联合中类型的键交集,因此

type AnimalKeys = keyof Animal // is "species" | "owner"

Omit 的实现方式如下:

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

1
这真是让人大开眼界,非常感谢!针对任何参考此问题的人 - 我建议你也阅读一下链接的文档。基本上,“对于T扩展U? X:Y的实例化,使用类型参数A | B | C解析为(A扩展U?X:Y)|(B扩展U?X:Y)|(C扩展U?X:Y)”。 - reesaspieces

7

我一直使用这个通用方法来成功地覆盖React组件中的props:

type Overwrite<T, NewT> = Omit<T, keyof NewT> & NewT;

export type TouchableAvatarPropTypes = Overwrite<AvatarPropTypes, {
    /** Function executed when avatar is pressed. */
    onPress: () => void;
    /** Size of the avatar */
    size: number;
}>

你可以使用一些相当简洁的单行代码进行导出。


6

如果您真的想坚持使用类型而不是接口,您可以使用泛型来避免重复:

type BaseAnimalParams<T extends BaseAnimal> = Omit<T, 'owner'> & {
    owner: string;
}

type AnimalParams = BaseAnimalParams<Dog> | BaseAnimalParams<Cat>;

谢谢!我接受了另一个答案,因为它更符合DRY原则(避免手动创建另一个联合类型),但你也得到了我的赞同。 - reesaspieces
1
是的,完全同意。我之前没有想过使用分布式条件类型。 - Robby Cornelissen
1
我喜欢这个解决方案,因为它还可以很好地进行泛化,例如请见 https://dev59.com/qVgR5IYBdhLWcg3wd9K5#55032655 中的 Modify - phil294

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接