首页 > 解决方案 > 使用字符串枚举来扩展 keyof

问题描述

在使用字符串枚举来索引类型时,我很难理解它们的行为方式。似乎有时 TS 会识别出字符串枚举的值是keyof某种类型的扩展,而其他时候则不会。为了显示:

enum Key {
    FOO = "foo",
}

type MyObj = {
    foo: string
}

因此,一个类型的属性的枚举(即,您可以索引obj:MyObjusing obj[Key.FOO])。

现在我想定义另一种类型,它将在 MyObj 类型中查找一个值以确定它是否是该类型的键,如果是,则使用该键值的类型:

type FunctionReturn<T extends Record<string, any>,U extends keyof T> = {
    returnValue: U extends keyof T ? T[U]: never 
}

但是,如果我按如下方式使用该类型,则会出现错误。

function myFunction(value: MyObj[Key.FOO]):FunctionReturn<MyObj, Key.FOO>{
    return {returnValue: "a string"}
}

即,TS 在键入 returnValue 时找不到满足的U extends key of T(其中 U 是枚举成员),即使它接受它满足 FunctionReturn 类型的约束。

如果我将实际的字符串文字作为参数传递,错误就会完全消失。

function myOtherFunction(value: MyObj[Key.FOO]):FunctionReturn<MyObj, "foo">{
    return {returnValue: "a string"}

这是为什么?在我看来,如果 TS作为通用约束中Key.FOO的有效扩展除外keyof MyOb,它也应该满足条件。我错过了什么?

这里是游乐场。

非常感谢,一如既往。

标签: typescript

解决方案


你可以使用这样的技巧:

enum Key {
    FOO = "foo",
}

type MyObj = {
    foo: string
}

/*
  I've added V generic parameter to tell TS to use U as keyof T, not as something else. 
  Now it works as expected.
*/
type FunctionReturn<T extends Record<string, any>,U extends keyof T, V = T[U]> = {
    returnValue: U extends keyof T ? V: never 
}

function myFunction(value: MyObj[Key.FOO]):FunctionReturn<MyObj, Key.FOO>{
    return {returnValue: "a string"}
}

function myOtherFunction(value: MyObj[Key.FOO]):FunctionReturn<MyObj, "foo">{
    return {returnValue: "a string"}
}

一些解释:

让我们从头开始。该函数期望U extends keyof T作为第二个类型变量。这意味着您可以传递任何可分配给的东西,keyof T也意味着U在函数执行中可以是任何可分配给的类型keyof T。当您通过Key.FOOasU时,TS 检查可分配性,keyof T它将被通过。然后,TS 推断Uas的类型Key(这很重要)。然后,由于U extends keyof T将解析为 true,TS 将解析T[U]T[Key]. 然后,解析T[key]TS 将与约束 相交keyof T,像这样T[Key & keyof T],反过来解析为T[never]。最有趣的是,这样的交集Key.FOO & "foo"会被解析到never。为什么要never? 好吧,可能是因为严格的规则,就像 TS 中除 enum 本身之外的任何其他类型的交集枚举将被解析为never.


为什么建议的方法有效:

当你FunctionReturn这样使用时FunctionReturn<MyObj, Key.FOO>,TS 首先解析泛型参数:

T = MyObj,
U = Key.FOO,
V = MyObj[Key.FOO] = string

因为真正的分支内部没有索引访问,所以不需要以某种方式解决它,TS 只是在真正的分支中使用类型字符串。


推荐阅读