Typescript 根据同一对象中的属性推断函数的对象键
Posted
技术标签:
【中文标题】Typescript 根据同一对象中的属性推断函数的对象键【英文标题】:Typescript infer object keys for function based on properties in the same object 【发布时间】:2020-12-28 17:27:01 【问题描述】:编辑:重新编写问题以使其更加清晰,并添加了一些额外的示例。
我想创建一个大对象(这是底层系统的要求),我想将该对象的两个元素之间的一种关系建模为由 TypeScript 引擎自动推断的 TypeScript 接口/类型。
我的对象如下所示:
selection:
name: "",
email: ""
,
handler: (values) =>
// Do something
我想要定义一个类型,以便将selection
对象中定义的键(在此示例中为name
和email
,但可能更多)被推断为values
上的键对象。
例如:
selection:
name: "",
email: ""
,
handler: (values) =>
values.name // Valid
values.email // Valid
values.foobar // Not valid
// or
selection:
name: "",
email: ""
address: "",
role: ""
,
handler: (values) =>
values.name // Valid
values.email // Valid
values.address // Valid
values.role // Valid
values.foobar // Not valid
使用这种类型:
interface HandlerObject<TSelection extends [key: string]: string >
selection: TSelection
handler?: (selection: [key in keyof TSelection]: any ) => any
只要我先明确设置通用TSelection
,我就可以键入对象:
interface Selection
name: string
email: string
const obj: HandlerObject<Selection> =
selection:
name: "",
email: ""
,
handler: (values) =>
values.name // Valid
values.email // Valid
values.foobar // Not valid
然而,对我来说,似乎 TypeScript 应该能够根据我在selection
属性中键入的内容推断出TSelection
本身。虽然我当然可能是错的。我推测我应该能够做到这一点:
const obj: InferredHandlerObject =
selection:
name: "",
email: ""
,
handler: (values) =>
values.name // Valid
values.email // Valid
values.foobar // Not valid
并且仍然以某种方式获得name
和email
的正确类型推断。这是可能的还是我需要明确输入选择?
原始问题
// SomeType is just a placeholder. This is where I want the proper type to go.
const obj: AType =
selection:
name: "something", // The value here is irrelevant. The key is important
email: "something elese" // The value here is irrelevant. The key is important
,
handler: (selection) =>
selection.name // selection only contains keys from the selection object defined above
有没有一种方法可以使用 Typescript 以这样的方式键入此内容,即选择 argument
始终包含与 selection
属性中定义的键相同的键?参数中键的值也不应该相同。
我试过了:
interface HandlerObject<TSelection extends [key: string]: string >
selection: TSelection
handler?: (selection: [key in keyof TSelection]: any ) => any
但它需要我明确指定泛型。
我希望它根据selection
属性自动推断TSelection
。
【问题讨论】:
SomeType
的定义是什么?
它只是一个占位符,表示正确的类型应该放在哪里。我已通过澄清更新了问题。
嗯,这是个问题。如果您没有obj
的类型,则无法在对象初始化程序中引用它来获取类型信息。你得到 "'obj' 隐含类型 'any' 因为它没有类型注释并且在其自己的初始化程序中直接或间接引用。(7022)"
看起来是这样的:typescriptlang.org/play?#code/…
【参考方案1】:
您曾说过SomeType
是“属性类型应放在的位置的占位符”。如果obj
是一次性的,则根本不需要类型; TypeScript 将从对象初始化器中推断出它。但是我们需要将它拆分一下,否则你会试图从它自己的初始化程序中引用它,而 TypeScript 不会让你这样做。你得到 "'obj' 隐含类型 'any' 因为它没有类型注释并且在其自己的初始化程序中直接或间接引用。(7022)" (playground link)。
下面是我们如何在类型声明中使用 typeof
来做到这一点,方法是分开一点:
const initialSelection =
name: "select.name",
email: "select.email"
;
const obj =
selection: initialSelection,
handler: (selection: typeof initialSelection) =>
selection.name // This works
;
Playground Link
如果您想避免在该范围内出现initialSelection
,您可以将其包装在一个函数中:
const obj = (() =>
const initialSelection =
name: "select.name",
email: "select.email"
;
return
selection: initialSelection,
handler: (selection: typeof initialSelection) =>
selection.name // This works
;
);
Playground Link
【讨论】:
这接近我想要的,但我希望 TypeScript 可以直接推断另一个对象属性的类型,从而避免需要匿名函数或与实际对象分开的显式定义。 @Thomas - 很确定你不能(我很遗憾看到)。尝试时 TypeScript 的错误非常明显(我很高兴看到)。 :-) 这就是我害怕的。感谢您帮助我调查。【参考方案2】:如果您坚持将所有内容都放在一个对象中,则需要像这样定义SomeType
:
interface SomeType
selection:
name: string;
email: string;
,
handler: (selection: SomeType['selection']) => void;
const obj: SomeType =
selection:
name: "select.name",
email: "select.email"
,
handler: (param) =>
param.name;
param.email;
Playground
【讨论】:
理想情况下,我希望将其保存在单个对象中,因为这就是我正在使用的框架的运行方式 (Sanity.io)。我知道我可以这样输入或使用多种类型,但我希望可以直接推断它。 TypeScript 应该已经拥有所有必要的数据。 只是为了清楚。我完全不介意定义类型,但我想避免在使用 selection 属性时为它定义新类型。 @Thomas 认为这是不可能的,但你可以使用像 this 这样的泛型。 还有,这个是一个对象,接口不是对象。 我知道我可以使用泛型,但这仍然需要我重新定义恕我直言应该可由 TypeScript 本身推断的属性。现在我将简单地坚持“任何”,因为输入两次很快就会变得烦人。无论如何感谢您的帮助。【参考方案3】:不完全确定您追求的是什么,但我假设您知道“选择”将进入什么内容,并且可能会因情况而异,您希望确保处理程序仅使用现有属性。
interface SomeType<T>
selection: T,
handler: (selection: T) => void
type User
name: string;
email: string;
const obj: SomeType<User> =
selection:
name: "select.name",
email: "select.email"
,
handler: (selection) =>
selection.name // selection only contains keys from the selection object defined above
【讨论】:
我已经更新了这个问题,希望能让事情变得更清楚。我试图让 TypeScript 为我推断 T 的键的繁重工作,因为我在对象本身中明确定义它们。【参考方案4】:无论出于何种原因,泛型类型推断对函数的操作与对类型的操作不同。
解决办法如下:
function isObj<O>(obj: selection: O; handler: (selection: [key in keyof O]: any ) => any )
return obj;
const obj = isObj(
selection:
name: '',
email: '',
,
handler(values)
values.name; // Valid
values.email; // Valid
values.foobar; // Not valid
,
);
obj;
【讨论】:
以上是关于Typescript 根据同一对象中的属性推断函数的对象键的主要内容,如果未能解决你的问题,请参考以下文章