在 TypeScript 中,我可以指定对象字段的类型,同时仍能推断出文字键类型吗?

In TypeScript, can I specify the type of object fields while still getting the literal key types inferred?

我想做的是定义某种“丰富的枚举”,其中每个枚举键都链接到一些我想指定其类型的数据。

例如,像这样:

const Seasons = {
    winter: { temperature: 5, startMonth: "December" },
    spring: { temperature: 20, startMonth: "March" },
    summer: { temperature: 30, startMonth: "June" },
    fall: { temperature: 15, startMonth: "September" },
} as const

这个声明很好,让我可以做这样的事情:

type Season = keyof typeof Seasons // "winter" | "spring" | "summer" | "fall"

甚至像

这样的类型保护
function isSeason(s: string): s is Season {
    return Object.keys(Seasons).includes(s)
}

不过,我不能做的是让编译器检查所​​有“季节定义”是否具有给定类型。如果我这样定义:

type SeasonData = typeof Seasons[Season]

那么SeasonData是所有定义类型的并集——不管它们是否具有相同的形状。

所以我正在寻找一种语法上非冗余且轻便的方式来定义如下内容:

const Seasons: EnumWith<{temperature: number, startMonth: string}> = ... // as before
               ^^^^^^^^ <- to be defined!

特别是,我试图不必在任何其他结构(接口或数组)中重复季节列表,而是直接从对象定义中推断出类型 Season(尽管听说替代方案是总是好的!)。

可以做什么?

我不确定我是否完全理解您的用例,我对您的类型之间的关系建模的方式类似于以下内容

type Season =
  | "winter"
  | "spring"
  | "summer"
  | "fall"

type Month =
  | "January"
  | "February"
  | "March"
  | "April"
  | "May"
  | "June"
  | "July"
  | "August"
  | "September"
  | "October"
  | "November"
  | "December"

type SeasonStruct = {
  temperature: number
  startMonth: Month
}

type Seasons = { [K in Season]: SeasonStruct }

const seasons: Seasons = {
    winter: { temperature: 5, startMonth: "December" },
    spring: { temperature: 20, startMonth: "March" },
    summer: { temperature: 30, startMonth: "June" },
    fall:   { temperature: 15, startMonth: "September" },
}

这应该为您提供了足够的构建块来表示您域中所需的一切,希望对您有所帮助。

刚刚找到了一种有点复杂的方法来从键中提取文字类型信息,同时仍然检查值:

function EnumWith<P>() {
    return function <K extends keyof any, R extends Record<K, P>>(defs: R): R {
        return defs
    }
}

允许这样写:

const Seasons = EnumWith<{
    temperature: number
    startMonth: Month // defined as in bugs's answer
}>()({
    winter: { temperature: 5, startMonth: "December" },
    spring: { temperature: 20, startMonth: "March" },
    summer: { temperature: 30, startMonth: "June" },
    fall: { temperature: 15, startMonth: "September" },
})
type Season = keyof typeof Seasons

关键是发现K extends keyof any可以让你在一个泛型签名中捕获key的类型,并将两个泛型类型分成两个函数调用,这样我们就可以指定一个,让另一个被推断(目前不可能在 TypeScript 中的单个函数调用中)。

所以,当然,}>()({ 行有点碍眼……