如何将条件类型从TypeScript中的值转换为不同的条件类型?

yyyllmsg  于 2023-05-23  发布在  TypeScript
关注(0)|答案(1)|浏览(247)

我试图将一个条件类型转换成一个基于“值”(JS变量,而不是类型本身)的不同条件类型,或者可能与相关的泛型类型组合。我可能会删除其中一个类型,这样我就不需要进行转换,但是我正在寻找这种方式是否可行。
它可能归结为基于值/变量来表达Payload extends void ? { ... } : { ... };,而不使用as或不同的TS转义阴影。我不知道这是否可能。
参见TS Playground

interface CustomConfig {}

export type PayloadWithMeta<Payload> = Payload extends void
    ? CustomConfig
    : { payload: Payload } & CustomConfig;

type NewPayloadFormat<Payload> = Payload extends void
    ? { config: CustomConfig }
    : { payload: Payload; config: CustomConfig };

// how to convert PayloadWithMeta to NewPayloadFormat?
function converter<Payload>(payloadWithMeta: PayloadWithMeta<Payload>) {
  // extract config
  const config = (() => {
    if ("payload" in payloadWithMeta) {
      const { payload, ...config } = payloadWithMeta;
      return config as CustomConfig;
    }
    return payloadWithMeta;
  })();

  // how do we apply the conditional types from values?
  return ("payload" in payloadWithMeta
    ? { payload: payloadWithMeta.payload, config }
    : { config }
  ) /* as NewPayloadFormat<Payload> <- this is required otherwise error */ satisfies NewPayloadFormat<Payload>;
}

// usage
const result = converter({
  payload: "some payload"
});
yhived7q

yhived7q1#

TypeScript无法对依赖于generic类型参数的conditional types执行太多有用的推理,例如converter()主体中的PayloadWithMeta<P>NewPayloadFormat<P>。编译器通常会将泛型条件类型视为 opaque,并且每当您尝试为其分配任何尚未知道的相同类型时都会抱怨。如果类型只是“等价”而不是完全相同,则赋值将失败。或者,编译器将决定通过扩展泛型类型参数到它们的约束来“过早地评估”类型,这可能会丢弃您需要的信息,也可能不会丢弃。现在,编译器无法对泛型函数中的类型参数进行足够的细化,以“智能”地处理条件类型。目前,这是TypeScript的一般限制。可能GitHub中最适用的开放问题是microsoft/TypeScript#33912,它专门讨论了一个函数能够返回一个没有类型Assert的通用条件类型。
所以,现在,如果你想保持你的实现原样,你需要使用类型Assert或类似的东西(例如,单调用签名重载),并从本质上放弃编译器验证的类型安全。只要您理解验证类型安全的角色已经转移到您身上,这可能是好的。
如果您想要更多的编译器验证类型安全性,对于这个特定的示例,您可以重构为一个版本,使泛型条件部分在整个过程中完全不透明。也就是说,将PayloadWithMeta<P>NewPayloadFormat<P>都写成一个通用Payload<P>类型的函数,您只需随意移动该类型,而无需尝试检查。可能像这样:

type Payload<P> = P extends undefined ? { payload?: never } : { payload: P };
type PayloadWithMeta<P> = CustomConfig & Payload<P>;
type NewPayloadFormat<P> = { config: CustomConfig } & Payload<P>

function converter<P = undefined>(payloadWithMeta: PayloadWithMeta<P>): NewPayloadFormat<P> {
  const { payload, ...config } = payloadWithMeta;
  return { ...payloadWithMeta, config };
}

这是因为在converter()中的destructuring assignment中,config被视为类型CustomConfig,而payload被视为类型Payload<P>,不管是什么。然后,我们将spreadpayloadWithMeta返回到一个新的对象,该对象具有config属性,表示为交集。在任何时候,编译器都不会试图理解Payload<P>内部发生了什么。
无论如何,这种重构对于其他示例可能并不总是可行的,或者可能不适合特定的用例。所以这里的一般答案是不幸的,对于泛型条件类型,你将比编译器更聪明,并且应该相应地使用类型Assert,至少从TypeScript 5.0开始。
Playground链接到代码

相关问题