简体   繁体   English

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

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

What I'm trying to do is to define some kind of "rich enumeration", where each enum key is linked to some data whose type I want to specify.我要做的是定义某种“丰富的枚举”,其中每个枚举键都链接到我想指定其类型的一些数据。

Eg, something like this:例如,像这样:

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

This declaration is nice and lets me do things like:这个声明很好,让我可以做如下事情:

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

and even a type guard like甚至像

function isSeason(s: string): s is Season {
    return Object.keys(Seasons).includes(s)
}

What I cannot do, though, is get the compiler to check that all “season definitions” have a given type.但是,我不能做的是让编译器检查所有“季节定义”是否具有给定的类型。 If I define this:如果我定义这个:

type SeasonData = typeof Seasons[Season]

then SeasonData is the union of the type of all definitions—no matter if they have the same shape or not.然后SeasonData是所有定义类型的并集——无论它们是否具有相同的形状。

So I'm looking for a syntactically non-redundant and light way to define something like:因此,我正在寻找一种语法上非冗余且轻便的方式来定义以下内容:

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

Especially, I'm trying not to have to repeat the list of seasons in any other structure (interface or array) and to directly infer the type Season from the object definition (though hearing about alternative is always good.).特别是,我试图不必在任何其他结构(接口或数组)中重复季节列表,并直接从 object 定义推断类型Season (尽管听到替代总是好的。)。

What could be done?可以做些什么?

I'm not sure I fully understand your use case, the way I'd model the relationship between your types is something like the following我不确定我是否完全理解您的用例,我会 model 的方式您的类型之间的关系类似于以下内容

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" },
}

This should give you enough building blocks to represent everything you need in your domain, hope it helps.这应该为您提供足够的构建块来代表您在域中需要的所有内容,希望对您有所帮助。

Just found a somewhat convoluted way to extract the literal type information from the keys while still checking the values:刚刚找到了一种有点复杂的方法来从键中提取文字类型信息,同时仍然检查值:

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

which allows to write this:这允许这样写:

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

The key was to find out that K extends keyof any lets you capture the type of the keys in a generic signature, and dividing the two generic types into two function calls so that we can specify one and let the other be inferred (currently not possible in a single function call in TypeScript).关键是要发现K extends keyof any可以让你在泛型签名中捕获键的类型,并将这两种泛型类型划分为两个 function 调用,以便我们可以指定一个并推断另一个(目前不可能在 TypeScript 中的单个 function 调用中)。

So, granted, the line }>()({ is a bit of an eyesore…所以,当然,这行}>()({有点碍眼……

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM