通用枚举类型保护

huangapple go评论56阅读模式
英文:

Generic enum typeguard

问题

I want to write a generic type guard for enums but this doesn't work so far in TS 5.0.4

function isEnumValue<T>(enumType: T, value: unknown): value is T[keyof T] {
  return Object.values(enumType).includes(value as T[keyof T]);
}
//-----------------------^--------------------------
//             No overload matches this call

enum Sort {
  A,
  B
}
function isSort(value: unknown): value is Sort {
  return isEnumValue(Sort, value);
}

enum Order {
  ASC,
  DESC
}
function isOrder(value: unknown): value is Order {
  return isEnumValue(Order, value);
}

Playground

Maybe I can have a hint? 通用枚举类型保护

英文:

I want to write a generic type guard for enums but this doesn't work so far in TS 5.0.4

function isEnumValue&lt;T&gt;(enumType: T, value: unknown): value is T[keyof T] {
  return Object.values(enumType).includes(value as T[keyof T]);
}
//-----------------------^--------------------------
//             No overload matches this call

enum Sort {
  A,
  B
}
function isSort(value: unknown): value is Sort {
  return isEnumValue(Sort, value);
}

enum Order {
  ASC,
  DESC
}
function isOrder(value: unknown): value is Order {
  return isEnumValue(Order, value);
}

Playground

Maybe I can have a hint ? 通用枚举类型保护

答案1

得分: 1

以下是您要翻译的内容:

我将假设您只想解决“no overload matches this call”错误,并且函数的实现在运行时按您的意图工作... 即使数值枚举具有反向映射,其中 Object.values(enumType).includes(value)value 是枚举 之一时会产生错误的正误。 有方法可以解决这个问题,但我考虑在此之外。


您遇到的问题是您的泛型类型参数 T 没有约束,因此可以是任何类型,包括其领域中带有 nullundefined 的类型。 因此,enumType 参数也可以是这种类型。

但是TypeScript库的类型定义用于the Object.values()方法如下:

interface ObjectConstructor {      
  values<T>(o: { [s: string]: T } | ArrayLike<T>): T[];
  values(o: {}): any[];
}

其中参数至少必须可分配给类型 {},这允许几乎任何东西,除了 nullundefined。 有关更多信息,请参见 https://stackoverflow.com/q/59135229/2887218

因此,上述两个Object.values()的调用签名都不合适。 T 显然不受限于 {[s: string]: unknown} | ArrayLike<unknown>},这是第一个调用签名所需的,也不受限于 {},这是第二个调用签名所需的。

换句话说:因为没有什么能阻止某人调用

isEnumValue(null, "a"); 

编译器不想让您将 enumType 传递给 Object.values()


因此,修复此问题的方法是将 T 约束为非可为空的 {} 类型:

function isEnumValue<T extends {}>(enumType: T, value: unknown): value is T[keyof T] {
  return Object.values(enumType).includes(value as T[keyof T]); // okay
}

这样可以工作,因为现在 isEnumValue() 不能使用可能为 null 的 enumType 参数调用。 如果您调用 isEnumValue(null, "a"),将在调用站点得到编译器错误:

isEnumValue(null, "a"); // error
// Argument of type 'null' is not assignable to parameter of type '{}'.

看起来不错!

代码示例链接

英文:

I'm going to assume that you just want to resolve the "no overload matches this call" error and that the implementation of the function works as you intend at runtime... even though numeric enums have reverse mappings where Object.values(enumType).includes(value) will give a false positive when value is one of the enum keys. There are ways to work around that but I'm considering this out of scope here.


The problem you're running into is that your generic type parameter T is not constrained, and therefore could be any type at all, including those with null or undefined in their domain. And so the enumType parameter can be of such a type.

But the TypeScript library typings for the Object.values() method are like

interface ObjectConstructor {      
  values&lt;T&gt;(o: { [s: string]: T } | ArrayLike&lt;T&gt;): T[];
  values(o: {}): any[];
}

where the parameter must at least be assignable to the type {}, which allows just about anything except for null and undefined. See https://stackoverflow.com/q/59135229/2887218 for more information.

So neither of the two call signatures for Object.values() shown above is appropriate. T is certainly not constrained to {[s: string]: unknown} | ArrayLike&lt;unknown&gt;} as needed for the first call signature, and it is not constrained to {} as needed for the second call signature.

In other words: since nothing stops someone from calling

isEnumValue(null, &quot;a&quot;); 

the compiler doesn't want to let you pass enumType into Object.values()


The fix for this is therefore to constrain T to be the non-nullable {} type:

function isEnumValue&lt;T extends {}&gt;(enumType: T, value: unknown): value is T[keyof T] {
  return Object.values(enumType).includes(value as T[keyof T]); // okay
}

This works because now isEnumValue() cannot be called with a possibly-nullish enumType argument. If you call isEnumValue(null, &quot;a&quot;) you'll get a compiler error at the call site:

isEnumValue(null, &quot;a&quot;); // error
// Argument of type &#39;null&#39; is not assignable to parameter of type &#39;{}&#39;.

Looks good!

Playground link to code

huangapple
  • 本文由 发表于 2023年5月14日 07:39:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/76245275.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定