【问题标题】:Prevent type annotation from type widening a const expression防止类型注释类型扩大 const 表达式
【发布时间】:2020-07-08 22:11:05
【问题描述】:

如何保持文字表达式常量(通过 const 断言),但仍针对类型对其进行类型检查以防止缺少/多余的属性?

换句话说,如何防止类型注释覆盖as const断言,扩大类型?

我或多或少了解正在发生的事情,并且我已经在聊天中询问过,所以我很确定这没有解决方案(在类型土地上),但也许有一个我不知道的 hack关于。

用例:

我需要定义一个配置,我可以从中根据值有条件地推断类型,但同时我想确保配置包含与其所基于的类型完全相同的键。

这是一个最小的例子。 State 类型是键的蓝图,config 对象应该对这些键进行类型检查。但是,我还需要 config 是一个常量,以便我可以深入了解它并获得单元联合类型,而不是扩大的 string 类型。

playground

type State = Readonly<{
    a: number;
    b: string;
}>;

const config: Record<keyof State, { value: string }> = {
    a: { value: "aaa" },
    b: { value: "bbb" }
} as const;

// I want this to be "aaa" | "bbb"
type ConfigValues = (typeof config)[keyof typeof config]["value"];

我可以这样做:

const config = {
    a: { value: "aaa" },
    b: { value: "bbb" }
} as const;

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const config_check: Record<keyof State, any> = config;

但以上只会检查缺少的道具,而不是多余的道具:/。

请注意,这是一个简单的示例。在现实世界中,配置更复杂,我想推断的类型是基于值的条件。

另外,我已经多次遇到这种问题模式,所以它看起来不像是边缘情况。

【问题讨论】:

    标签: javascript typescript


    【解决方案1】:

    您可以使用通用辅助函数来约束类型,同时保持其狭窄以进行推理:

    function createConfig<P extends string, T extends Record<keyof State, { value: P }>>(
        cfg: { [K in keyof T]: K extends keyof State ? T[K] : never }) {
        return cfg
    }
    
    const config = createConfig({
        a: { value: "aaa" },
        b: { value: "bbb" },
        c: { value: "ccc" } // conditional type error akin to excess property check
    })
    
    type keys = (typeof config)[keyof typeof config]["value"]; // "aaa" | "bbb"
    type keyA = (typeof config)["a"]["value"] // "aaa"
    

    Playground

    【讨论】:

    • 行不通,因为value 的推断类型是所有可能值的联合,而不是文字值。您可以通过执行(typeof config)["a"]["value"]; 来检查这一点,它解析为"aaa" | "bbb" 而不是"aaa"。换句话说,返回值类型不是常量字面量类型,而是cfg 参数注释的类型,这意味着我们回到了我原来的问题。这意味着我将无法根据文字对象中的实际值推断条件类型。这就是为什么我注意到我的示例被简化了。
    • 非常酷,谢谢!不过,P 参数似乎是额外的。现在剩下的就是添加对partial type params 的支持,这样我们就可以轻松地传递State 的类型,而不是硬编码它。现在唯一的方法似乎是将配置分解为一个变量,然后执行createConfig&lt;State, typeof cfg&gt;(cfg)。但这是一个单独的问题。
    • 是的,或者您可以对函数进行 curry:createConfig&lt;State&gt;()(cfg),其中第一个函数部分可能会采用一些默认状态。顺便说一句:P 是故意的。否则 TS 推断 string 而不是传入的字符串文字类型(这是编译器的工作方式)。如果您不想从调用方使用as const,这是一个巧妙的技巧。
    • 哦,很好。我确实使用了as const,所以这就是我错过它的原因:)。关于咖喱的好点!
    猜你喜欢
    • 2019-12-21
    • 2016-10-25
    • 1970-01-01
    • 1970-01-01
    • 2021-11-22
    • 2023-03-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多