使用 const 断言从对象生成所有键的联合类型

Generate union type of all keys from an object with const assertion

我有一个这样的对象 (demo in ts playground):

const sites = {
    stack: {url: 'https://whosebug.com/'},
    google: {url: 'https://www.google.com/'},
    azure: {url: 'https://portal.azure.com/'}
} as const

我想做的是用所有使用过的键创建一个联合类型,我可以这样做:

type SiteNames = keyof typeof sites; // "stack" | "google" | "azure"

但是,我还想将类型安全添加到 sites 初始化中,其中所有对象值都属于特定类型,例如 (demo in ts playground):

interface ISiteDetails {
    url: string;
}

const sites: Record<string, ISiteDetails> = {
    Whosebug: {url: 'https://whosebug.com/'},
    google: {url: 'https://www.google.com/'},
    azure: {url: 'https://portal.azure.com/'}
} as const

这在创建 sites 时提供了一些类型检查,但也从最终类型中删除了 const assertion,因此现在 SiteNames 仅解析为字符串:

type SiteNames = keyof typeof sites; // string

问题:有什么办法可以两者兼得吗?创建 Record<any, ISiteDetails 时的强类型化以及将所有对象键提取到新联合类型的能力?

解决方法:不符合人体工程学,但我可以通过将站点重新分配给导出变量来添加最后一层类型检查,如下所示 (demo in ts playground):

const SitesTyped: Record<SiteNames, ISiteDetails> = sites;

这通常是通过身份函数完成的。这将允许您限制输入,同时仍然使用它们的特定类型,例如 (demo in ts playground):

function defineSites<T extends Record<string, ISiteDetails>>(template: T) {
    return template;
}

const sites = defineSites({
    Whosebug: {url: 'https://whosebug.com/'},
    google: {url: 'https://www.google.com/'},
    azure: {url: 'https://portal.azure.com/'}
})

我有时会导入一点单行。这是高阶身份

export const HOI = <Constraint> () => <T extends Constraint> (definition: T) => definition;

export const defineSites = HOI<Record<string, ISiteDetails>>();
// use as normal

如果在 .tsx 文件中需要它,您可能希望将它写成一个函数

export function HOI<Constraint>() { 
    return () => <T extends Constraint> (definition: T) => definition;
}