typescript 类型检查常量的键而不丢失类型

zbq4xfa0  于 2023-06-07  发布在  TypeScript
关注(0)|答案(2)|浏览(177)

我想有一个常量引用接口的键。我希望对这个常量进行类型检查,以确保键的名称正确,并且我还希望将它们键入为文字。
如果我执行以下操作:

interface MyInterface {
  keyA: string;
  keyB: string;
}

export const MY_KEYS: Record<string, keyof MyInterface> = {
  CONSTANT_KEY_A: 'keyA',
  CONSTANT_KEY_B: 'keyB',
} as const;

MY_KEYS.CONSTANT_KEY_A

那么MY_KEYS.CONSTANT_KEY_A的类型是'keyA' | 'keyB'。但我希望它是'keyA'
如果我删除Record<string, keyof MyInterface>,那么它就可以工作,但是我的键不再被类型检查为MyInterface的键。
你知道我该怎么做吗
例如,我可以添加第二个死变量来执行Record检查,并只使用as const保留第一个变量,而不将其转换为Record,但这非常冗长,而且不太清楚。
谢谢你!

qij5mzcb

qij5mzcb1#

通常在这种情况下,我会引入一个助手函数,它检查一个值是否可以分配给一个类型,而不会将该值扩展到该类型。helper函数的一般版本如下所示:

const checkType = <T>() => <U extends T>(u: U) => u;

然后让它检查你调用的特定类型,并手动指定该类型:

const checkKeys = checkType<Record<string, keyof MyInterface>>();

现在你有了一个函数,它只接受正确类型的值,当你做错了什么时,它会给予你预期的错误:

export const MY_KEYS = checkKeys({
  CONSTANT_KEY_A: 'keyA',
  CONSTANT_KEY_B: 'keyB',
  // CONSTANT_KEY_C: 'keyC', // error! '"keyC"' is not assignable to  '"keyA" | "keyB"'.
})

但函数的输出不按需要加宽:

MY_KEYS.CONSTANT_KEY_A // "keyA"

请注意,您甚至不需要as const,因为编译器认为您希望将对象文字解释为可分配给Record<string, keyof MyInterface>,因此不会将文字字符串值扩展为string。不过,如果as const在其他方面有帮助,您仍然可以使用它。
从概念上讲,这与您的“死变量”想法没有什么不同,当然在运行时,(() => u => u)()(someValue)是编写someValue的一种奇怪方式。但是在TypeScript中没有内置的“验证可分配性而不扩大”操作符(比如microsoft/TypeScript#30809?),这是我所知道的一般情况下最好的方法。
好吧,希望能帮上忙;祝你好运!
Playground链接到代码

klr1opcd

klr1opcd2#

对于使用typescript 4.9.5或更高版本的用户,只需使用satisfies(链接):

export const MY_KEYS = {
  CONSTANT_KEY_A: 'keyA',
  CONSTANT_KEY_B: 'keyB',
} as const satisfies Record<string, keyof MyInterface>;

MY_KEYS.CONSTANT_KEY_A
// (property) CONSTANT_KEY_A: "keyA"

相关问题