3

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

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

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

用例:

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

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

操场

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;

但是上面只会检查丢失的道具,而不是多余的道具:/。

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

此外,我已经多次遇到这个问题模式,所以它看起来不像是一个边缘案例。

4

1 回答 1

2

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

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"

操场

于 2020-07-08T13:05:04.363 回答