【问题标题】:Create an interface/type alias with an exhaustive list of properties that comes from a union type?使用来自联合类型的详尽属性列表创建接口/类型别名?
【发布时间】:2021-01-12 00:03:46
【问题描述】:

假设我有这个数组,它是我的动作名称的“真实来源”:

const ACTION_NAMES = ["ACTION_1","ACTION_1","ACTION_3"] as const;

// I'M USING "as const" SO I CAN GET A UNION TYPE OUT OF IT

type ACTION_NAMES = typeof ACTION_NAMES[number];

// THIS IS "ACTION_1" | "ACTION_2" | "ACTION_3"

现在我想要一个接口/类型,它应该具有所有这些属性(详尽无遗),如:

type SOME_TYPE = {
  [key: string]: unknown
}

interface ACTION_PAYLOADS {  // COULD ALSO USE type alias HERE
  ACTION_1: {propA: string, propB: number},
  ACTION_2: {propC: boolean},
  ACTION_3: {propD: string}
}

我可以使用extendACTION_PAYLOADS 接口的任何类型,以便Typescript 确保所有ACTION_1 2 and 3 属性都存在,并且它们的所有值都将extend SOME_TYPE 类型?

这个想法是,如果我缺少 ACTION_ 属性之一,并且其中一个值不是 SOME_TYPE 类型,Typescript 理想情况下应该抱怨。它也不允许我添加任何额外的属性,例如ACTION_4

感觉就像我在检查类型。但是我不能用一些映射类型来做吗?有没有办法做到这一点?

【问题讨论】:

    标签: typescript typescript-typings mapped-types


    【解决方案1】:

    如果您的接口不符合所需的约束,您可以使用一些辅助类型来触发编译器错误。它看起来像这样:

    type Extends<T extends U, U> = void;
    type MutuallyExtends<T extends U, U extends V, V = T> = void;
    
    type CheckKeysOfActionPayloads =
        MutuallyExtends<keyof ACTION_PAYLOADS, ACTION_NAMES>; // ok
    type CheckValuesOfActionPayloads =
        Extends<ACTION_PAYLOADS[ACTION_NAMES], SOME_TYPE>; // ok
    

    这里,CheckKeysOfActionPayloads 仅在 ACTION_PAYLOADS 的键和 ACTION_NAMES 类型相互扩展时才能编译...对于文字联合,这意味着它们需要相同。

    只有当ACTION_PAYLOADS(在ACTION_NAMES键处)的值可分配给SOME_TYPE时,CheckValuesOfActionPayload才会编译。


    为了具体起见,我将把 SOME_TYPE 更改为可能失败的东西,并且让我们确保 ACTION_NAMES 中包含 "ACTION_2"(与您的示例代码相反):

    const ACTION_NAMES = ["ACTION_1", "ACTION_2", "ACTION_3"] as const;
    type ACTION_NAMES = typeof ACTION_NAMES[number];
    type SOME_TYPE = {
        [key: string]: string | number | boolean
    }
    

    让我们看看如果我们搞砸了ACTION_PAYLOADS 会发生什么。首先,如果我们漏掉了一个键:

    interface ACTION_PAYLOADS {
        ACTION_1: { propA: string, propB: number },
        // where's ACTION_2?
        ACTION_3: { propD: string }
    }
    
    type CheckKeysOfActionPayloads =
        MutuallyExtends<keyof ACTION_PAYLOADS, ACTION_NAMES>; // error!
    //      ----------------------------------> ~~~~~~~~~~~~
    // Type "ACTION_2" is not assignable to type "ACTION_1" | "ACTION_3"
    

    或者如果我们有一个额外的密钥:

    interface ACTION_PAYLOADS {
        ACTION_1: { propA: string, propB: number },
        ACTION_2: { propC: boolean },
        ACTION_3: { propD: string },
        ACTION_4: { propE: number } // what's this?
    }
    
    type CheckKeysOfActionPayloads =
        MutuallyExtends<keyof ACTION_PAYLOADS, ACTION_NAMES>; // error!
    // ---------------> ~~~~~~~~~~~~~~~~~~~~~
    // Type "ACTION_4" is not assignable to type "ACTION_1" | "ACTION_2" | "ACTION_3"
    

    最后,如果键正确但值错误:

    interface ACTION_PAYLOADS {
        ACTION_1: { propA: string, propB: number },
        ACTION_2: { propC: Date }, // what's this?
        ACTION_3: { propD: string }
    }
    
    type CheckValuesOfActionPayloads =
        Extends<ACTION_PAYLOADS[ACTION_NAMES], SOME_TYPE>; // error!
    // -------> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // Type 'Date' is not assignable to type 'string | number | boolean'
    

    您可以看到,在所有这些情况下,您都会收到一个错误,希望可以用来帮助您修复代码。

    Playground link to code

    【讨论】:

    • 感谢您的回答,jcalz!一段时间以来,我一直在努力寻找解决方法。这样就行了。
    猜你喜欢
    • 2019-11-26
    • 2022-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-13
    • 2021-12-20
    • 1970-01-01
    • 2018-03-20
    相关资源
    最近更新 更多