给定一个从DB生成的类型,其中一个键是几个类型的联合,我想构造一个只包含联合子集的类型.我从联合中提取了所需的类型--如何重新构造基类型?或者我应该使用一种不同的方法?

// This is an example, I don't have control of this type; it's generated and
// therefore I can't rewrite this in a composable way.
type _GeneratedPersonType = {
  age: number;
  name: string;
  profession: {
    type: "doctor"
  } | {
    type: "actor"
  } | {
    type: "magician"
  }
}

type Profession = _GeneratedPersonType["profession"];
type ActorProfession = Extract<Profession, { type: "actor" }>
type MagicianProfession = Extract<Profession, { type: "magician" }>

// this DOES NOT WORK as written, but I want it to be
// { ..., profession: ActorProfession | MagicianProfession }
type ActorOrMagician = _GeneratedPersonType & ActorProfession & MagicianProfession;

推荐答案

TL;DR

type ActorOrMagician = _GeneratedPersonType & { profession: ActorProfession | MagicianProfession }

TypeScrip有intersection types个;交叉点类型可分配给它的所有组成类型.

例如,两个对象类型的交集将创建一个包含这两个类型的所有成员的对象类型:

type A = { a: number }
type B = { b: string }
type C = A & B
//   ^? { a: number; b: string }

你实际上已经try 过使用它,但并不完全正确.

type ActorOrMagician = _GeneratedPersonType & ActorProfession & MagicianProfession;

您正在try 将提取的profession类型与根类型and with each other相交.由于它们不兼容,结果是never*.

*类型为{ type: "actor" }{ type: "magician" }.一个类型不能同时赋值给"actor""magician",因此将它们相交产生{ type: never }.然后,TypeScrip将这种"不可能的类型"简化为never.

即使这是正确的,还有另一个问题:您直接将职业类型与根类型相交.这会将type属性添加到新类型的根,而不是将对象添加到profession属性.

So what you need to do is (A) use a union of the wanted professions and (B) use the correct structure.
The result is this:

type ActorOrMagician = _GeneratedPersonType & { profession: ActorProfession | MagicianProfession }

您可以看到我使用的是ActorProfessionMagicianProfessionunion,并将它们与the same structure as 102放在一个对象中.

TypeScript Playground


As I noted before, you don't need to omit the profession property from _GeneratedPersonType first: that is the job of the intersection.
Intersections create a type that is assignable to all of the constituent types, so the resulting type will be at least as "narrow" as the "narrowest" type included.

从一个简单的 case 开始,您可以看到它是如何工作的:

type Fruits = "apples" | "oranges" | "bananas"
type ApplesAndOranges = "apples" | "oranges"
type t1 = Fruits & ApplesAndOranges
//   ^? "apples" | "oranges"
// "bananas" is not assignable to "apples" | "oranges", so it is excluded.

你可以根据你的情况推断这一点:

type PersonType = {
  profession:
    | { type: "doctor" }
    | { type: "actor" }
    | { type: "magician" }
}

type t1 = PersonType & {
  profession: { type: "actor" } | { type: "magician" }
}

// We can look at just that `profession` property:

type Professions = 
  | { type: "doctor" }
  | { type: "actor" }
  | { type: "magician" }

type t2 = Professions & ({ type: "actor" } | { type: "magician" })
//   ^? { type: "actor" } | { type: "magician" }
// { type: "doctor" } is not assignable to { type: "actor" } | { type: "magician" }, so it is excluded.

// Therefore:
// t1 = {
//   profession:
//     | { type: "actor" }
//     | { type: "magician" }
// }

Typescript相关问答推荐

类型脚本泛型最初推断然后设置

打字类型保护递归判断

如何在Reaction路由v6.4加载器和操作中获得Auth0访问令牌?

如何在深度嵌套的Reaction路由对象中隐藏父级?

接口重载方法顺序重要吗?

`fetcher.load`是否等同于Get Submit?

在VSCode中显示eslint错误,但在终端中运行eslint命令时不显示?(Vite,React,TypeScript)

如何从输入中删除值0

类型推断对React.ForwardRef()的引用参数无效

缩小对象具有某一类型的任意字段的范围

TypeScrip-根据一个参数值验证另一个参数值

如何在不违反挂钩规则的情况下动态更改显示内容(带有状态)?

为什么数字是`{[key:string]:UNKNOWN;}`的有效密钥?

如何创建由一个帐户签名但使用另一个帐户支付的Hedera交易

在类型{}上找不到带有string类型参数的索引签名 - TypeScript

如何通过nx找到所有受影响的项目?

在Typescript 无法正常工作的 Three.js 中更新动画中的 GLSL 统一变量

我是否应该在 Next.js 中的服务器组件中获取秘密数据的所有函数中使用使用服务器?

如何为字符串模板文字创建类型保护

在 next.js 13.4 中为react-email-editor使用forwardRef和next/dynamic的正确方法