枚举映射附加约束的条件类型

Posted

技术标签:

【中文标题】枚举映射附加约束的条件类型【英文标题】:Conditional type for additional constraint on enum mapping 【发布时间】:2021-09-23 07:36:50 【问题描述】:

在我的项目中,我有两个枚举 SourceEnumTargetEnum。对于这两个枚举,都存在一个函数,该函数使用一些取决于枚举值的参数调用。预期参数的确切类型由SourceParamsTargetParams 两种类型映射定义。

enum SourceEnum 
  SOURCE_A = 'SOURCE_A',
  SOURCE_B = 'SOURCE_B'


enum TargetEnum 
  TARGET_A = 'TARGET_A',
  TARGET_B = 'TARGET_B',


interface SourceParams 
  [SourceEnum.SOURCE_A]:  paramA: string ;
  [SourceEnum.SOURCE_B]:  paramB: number ;


interface TargetParams 
  [TargetEnum.TARGET_A]:  paramA: string ;
  [TargetEnum.TARGET_B]:  paramB: number ;


function sourceFn<S extends SourceEnum>(source: S, params: SourceParams[S])  /* ... */ 

function targetFn<T extends TargetEnum>(target: T, params: TargetParams[T])  /* ... */ 

我有一个映射,其中包含一个函数来评估每个源值的目标值,我想要做的是,确保用于调用 sourceFn(x, params)params 对象也适用于调用 targetFn(mapping[x](), params) .为了实现这一点,我创建了这种类型:

type ConstrainedMapping = 
  [K in SourceEnum]: <T extends TargetEnum>() => (SourceParams[K] extends TargetParams[T] ? T : never) 
;

const mapping: ConstrainedMapping = 
  [SourceEnum.SOURCE_A]: () => TargetEnum.TARGET_A;
  // ...

但是像上面那样定义mapping会给我以下错误:

Type 'TargetEnum.TARGET_A' is not assignable to type ' paramA: string;  extends TargetParams[T] ? T : never'.

我的打字看起来很清楚,所以我真的不明白这里有什么问题。我想打字稿在某些时候无法缩小确切的枚举值。

有没有办法做到这一点?我目前正在使用 Typescript 4.2,但我也在 4.3 和 4.4-beta 上进行了尝试,并且都表现出相同的行为。非常感谢 4.2 中的解决方案,但我也可以在未来版本中提供解决方案。

【问题讨论】:

请提供更多致电targetFn(mapping[x](), params)sourceFn(x, params) 的示例。你从哪里得到x x 在这里只是一个SourceEnum 类型的任意值。这两个功能实际上并不太相关。我只是想以一种实际的方式表明SourceParams[typeof x] 应该可以分配给TargetParams[typeof mapping[x]()] 用于任何x: SourceEnum 【参考方案1】:

因此,您在这里期望 Typescript 看到 &lt;T extends TargetEnum&gt;(): SourceParams[K] extends TargetParams[T] ? T : never;,然后将 T 的所有可能值分配到此条件中,并在其为真时创建一个联合。

问题是,Typescript 没有这样做。它将T 视为未知,并且除了将 paramA: string; 替换为SourceParams[K] 之外,不会进一步评估此表达式。您正在寻找的分布类型仅出现在 Distributive Conditional Types 中。所以我们必须重写你的ConstrainedMapping 才能使用。

分布式条件类型是一个条件类型别名,其中没有任何参数受到约束。所以首先,我们需要一个实际的type 声明来表示这个返回值,我们不能把它放在更大的ConstrainedMapping 声明中。 (是的,这很奇怪——如果将类型提取到自己的别名中,其行为会有所不同,这是我对 Typescript 设计的最大问题之一。)就像这样:

type TargetWithMatchingParams<S, T> =
  S extends SourceEnum
    ? T extends TargetEnum
      ? SourceParams[S] extends TargetParams[T]
        ? T
        : never
      : never
    : never;

我们无法限制ST,因此我们必须在模板中使用更多条件。 (是的,这也很奇怪;改变这样的行为是相当违反直觉的。)我们也不能在这里简单地硬编码整个TargetEnum,即使这最终是我们想要的——分布必须是跨越一个不受约束的参数到类型别名。

当我们完成那个后,我们就可以在ConstrainedMapping中使用它了:

type ConstrainedMapping = 
  [S in SourceEnum]: () => TargetWithMatchingParams<S, TargetEnum>;
;

请注意,该函数不再是通用的(这是您的问题的一部分),我们通过将 TargetEnum 传递给 TargetWithMatchingParams 来实现您正在寻找的分布。

顺便说一句,如果您的真实案例像您的示例一样是静态的,则从 ConstrainedMapping 的定义中删除 () =&gt; 并使用 mapping[x] 而不是 mapping[x]() “调用”mapping 会稍微提高性能,并且可以说更容易阅读。

最后,这里有几个限制需要注意。

    extends SourceEnum 不起作用的泛型变量上调用 mapping。也就是说,targetFn(mapping[SourceEnum.SOURCE_A](), paramA: 'foo' ) 可以工作,但 Typescript 却被这样的东西呛住了:

    function bar<S extends SourceEnum>(src: S, params: SourceParams[S]) 
      targetFn(mapping[src](), params);
                               ^^^^^^
    //                         Argument of type 'SourceParams[S]'
    //                           is not assignable to parameter of type
    //                           'TargetParams[ConstrainedMapping[S]]'.
    
    

    也就是说,Typescript 不够聪明,无法真正理解SourceParamsTargetParams 之间的关系,并且无法识别任何有效的SourceParams 值将始终匹配对应的TargetParams

    在我们接受源联合和源参数联合的情况下,基本上是上述的非泛型版本,Typescript 允许不安全的值。考虑:

    function baz(src: SourceEnum, params: SourceParams[SourceEnum]) 
      targetFn(mapping(src), params);
    
    baz(SourceEnum.SOURCE_A,  paramB: 42 );
    

    这不会导致任何错误,即使我们有 SOURCE_AparamB 并且这是一个无效的组合。不过,归根结底,这只是对 Typescript 联合工作方式的限制——问题在于 baz 的定义或 SourceParams/TargetParams 的定义,如果 baz 调用 @ 则完全相同987654358@ 根本没有mapping 进入。

这里的太长,未阅读版本只是确保您使用特定枚举类型而不是整个枚举调用 sourceFntargetFn。 Typescript 不会跟踪单独变量之间的关系,因此它不会检查未知的 SourceEnum 和未知的 SourceParams[SourceEnum] 是否真的在一起,并且没有定义 mapping 可以解决这个问题。

【讨论】:

以上是关于枚举映射附加约束的条件类型的主要内容,如果未能解决你的问题,请参考以下文章

一对多映射和附加条件列不是外键的一部分

MYSQL创建表的约束条件(可选)

SQL中Unique约束有啥用啊?

SQL中Unique约束有啥用啊?

枚举与集合类型 约束条件

3 日期类型/字符类型/枚举类型/集合类型/约束条件