【问题标题】:Typescript infer object keys for function based on properties in the same objectTypescript 根据同一对象中的属性推断函数的对象键
【发布时间】:2020-12-28 17:27:01
【问题描述】:

编辑:重新编写问题以使其更加清晰,并添加了一些额外的示例。

我想创建一个大对象(这是底层系统的要求),我想将该对象的两个元素之间的一种关系建模为由 TypeScript 引擎自动推断的 TypeScript 接口/类型。

我的对象如下所示:

{
  selection: {
    name: "",
    email: ""
  },
  handler: (values) => {
    // Do something
  }
}

我想要定义一个类型,以便将selection 对象中定义的键(在此示例中为nameemail,但可能更多)被推断为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
  }
}

并且仍然以某种方式获得nameemail 的正确类型推断。这是可能的还是我需要明确输入选择?


原始问题

// 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/…

标签: typescript typescript-typings


【解决方案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;

【讨论】:

    猜你喜欢
    • 2021-11-03
    • 2022-08-09
    • 2022-12-18
    • 2020-11-13
    • 1970-01-01
    • 2019-12-09
    • 2020-01-01
    • 2020-04-14
    • 1970-01-01
    相关资源
    最近更新 更多