枚举值作为函数参数映射到特定的返回类型

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

Enum value as function parameter mapped to specific return type

问题

我创建了一个我正在遇到问题的小示例。我希望get函数接受一个枚举参数,并且该函数的返回值取决于该枚举值。我试图将枚举值映射到它的类型,在映射类型中,但我没有成功。

下面的代码不起作用-它似乎认为映射类型是一个联合类型。

type T1 = { name: string }
type T2 = { age: number }
enum Type { TypeOne, TypeTwo }

const get = <T extends Type>(type: T): {
    [Type.TypeOne]: T1
    [Type.TypeTwo]: T2
}[T] => type === Type.TypeOne ? { name: 'Brian' } : { age: 42 }

我在最后一行得到错误消息:

Type '{ name: string; } | { age: number; }' is not assignable to type '{ 0: T1; 1: T2; }[T]'.
  Type '{ name: string; }' is not assignable to type '{ 0: T1; 1: T2; }[T]'.
    Type '{ name: string; }' is not assignable to type 'T1 & T2'.
      Property 'age' is missing in type '{ name: string; }' but required in type 'T2'.ts(2322)

Fiddle链接

有什么建议吗?

英文:

I have created a small example of a problem I'm experiencing. I want the get function to take a enum parameter, and have the return value of the function be dependant on that enum value. I'm trying to map the enum value to it's type in a mapped type, but I'm not having any luck.

The code below doesn't work - it seems like it believes the mapping type to be a union type.

type T1 = { name: string }
type T2 = { age: number }
enum Type { TypeOne, TypeTwo }

const get = &lt;T extends Type&gt;(type: T): {
    [Type.TypeOne]: T1
    [Type.TypeTwo]: T2
}[T] =&gt; type === Type.TypeOne ? { name: &#39;Brian&#39; } : { age: 42 }

I get the error message on the last line:

Type &#39;{ name: string; } | { age: number; }&#39; is not assignable to type &#39;{ 0: T1; 1: T2; }[T]&#39;.
  Type &#39;{ name: string; }&#39; is not assignable to type &#39;{ 0: T1; 1: T2; }[T]&#39;.
    Type &#39;{ name: string; }&#39; is not assignable to type &#39;T1 &amp; T2&#39;.
      Property &#39;age&#39; is missing in type &#39;{ name: string; }&#39; but required in type &#39;T2&#39;.ts(2322)

Fiddle link

Any suggestions?

答案1

得分: 1

TypeScript目前无法使用控制流分析来影响泛型 类型参数。因此,虽然检查 type === Type.TypeOne 会影响 type 的表现类型,但泛型类型参数 T 将保持不受影响。

检查 type === Type.TypeOne 应该将 TT extends Type 缩小为 T extends Type.TypeOne,这似乎是"显而易见"的,但总的来说,这是不正确的。毕竟,T 可能 是完整的联合类型 Type。对于像 &lt;T extends Type&gt;(type1: T, type2: T) => void 这样的调用签名,假设检查 type1 === Type.TypeOne 意味着 type2 === Type.TypeOne 将是危险的。因此,可能需要采用其他方法,可能如此开放的功能请求中所描述。但目前还没有实现类似这样的功能。

在支持此功能之前,我会采用一种方法来实现返回索引访问类型的泛型函数,即实际上使用适当类型的对象来索引适当的索引。对于您的示例,可以看起来像这样:

const get = <T extends Type>(type: T): {
    [Type.TypeOne]: T1
    [Type.TypeTwo]: T2
}[T] => ({
    [Type.TypeOne]: { name: 'Brian' },
    [Type.TypeTwo]: { age: 42 }
}[type]);

如果真的需要的话,甚至可以使用getter,使对象仅在访问的成员时才进行惰性计算:

const get = <T extends Type>(type: T): {
    [Type.TypeOne]: T1
    [Type.TypeTwo]: T2
}[T] => ({
    get [Type.TypeOne]() { return { name: 'Brian' } },
    get [Type.TypeTwo]() { return { age: 42 } }
}[type]);

对于示例代码,这不会有太大影响,但如果原始函数具有副作用,或者预先计算每个可能的返回值太昂贵,您可能会希望这样做。

Playground链接到代码

英文:

TypeScript currently can't use control flow analysis to affect generic type parameters. So while checking type === Type.TypeOne will have an effect on the apparent type of type, the generic type parameter T will stubbornly remain unaffected.

It might seem "obvious" that checking type === Type.TypeOne should narrow T from T extends Type to T extends Type.TypeOne, but in general this would be incorrect. After all, T might be the full union type Type. For a call signature like &lt;T extends Type&gt;(type1: T, type2: T) =&gt; void, it would be dangerous to assume that checking type1 === Type.TypeOne would mean that type2 === Type.TypeOne. So some other approach is necessary, possibly as described in the open feature request at microsoft/TypeScript#33014. But for now, nothing like this is implemented.


Until and unless support for that is added, the way I'd approach implementing a generic function that returns an indexed access type is to actually index into an appropriately-typed object with the appropriate index. For your example that could look like:

const get = &lt;T extends Type&gt;(type: T): {
    [Type.TypeOne]: T1
    [Type.TypeTwo]: T2
}[T] =&gt; ({
    [Type.TypeOne]: { name: &#39;Brian&#39; },
    [Type.TypeTwo]: { age: 42 }
}[type]);

If you really need to, you could even write this with getters so that the object lazily computes only the member you are accessing:

const get = &lt;T extends Type&gt;(type: T): {
    [Type.TypeOne]: T1
    [Type.TypeTwo]: T2
}[T] =&gt; ({
    get [Type.TypeOne]() { return { name: &#39;Brian&#39; } },
    get [Type.TypeTwo]() { return { age: 42 } }
}[type]);

For the example code that wouldn't matter much, but you might want to do this if the original function has side effects or if pre-computing every possible return value is too expensive.

Playground link to code

huangapple
  • 本文由 发表于 2023年6月19日 23:43:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/76508181.html
匿名

发表评论

匿名网友

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

确定