【问题标题】:TypeScript: Typing generic interface's method parameter by reading a value from the generic typeTypeScript:通过从泛型类型读取值来键入泛型接口的方法参数
【发布时间】:2022-11-29 05:19:34
【问题描述】:

我想定义一个能够处理任何 Data 类型的通用接口。该接口有一个dataKey 属性,它的值只是一个keyof Data。它还有一个处理函数,它的参数类型应该和使用dataKeyData读取一个值的类型相同。应该是这样的,但这不起作用,因为 Data[dataKey] 不是有效的 TypeScript:

interface Handler<Data> {
    dataKey: keyof Data,
    handler: (value: Data[dataKey]) => void
}

有没有办法让它工作?我可以使用 any 类型而不是 Data[dataKey],但这并不能保证类型安全。

这是我想如何使用 Handler 接口的示例:

function handleData<Data extends object>(data: Data, handler: Handler<Data>) {
    const value = data[handler.dataKey];
    handler.handler(value);
}

interface Person {
    name: string,
    age: number,
}

const person: Person = {name: "Seppo", age: 56};
const handler: Handler<Person> = {dataKey: "name", handler: (value: string) => {
    // Here we know that the type of `value` is string,
    // as it is the type of reading `name` from the person object.
    // If I change dataKey to "age", the type of `value`
    // should be `number`, respectively
    console.log("Name:", value);
}}

handleData(person, handler);

【问题讨论】:

  • interface Handler&lt;Data, K extends keyof Data = keyof Data&gt; { - 然后在您的处理程序中使用Data[K]
  • 你不能真的用界面直接,但你可以创建一个以这种方式工作的联合类型。 Does this approach满足你的需求吗?如果是这样,我可以写一个答案来解释;如果没有,我错过了什么? (如果您回复,请通过@jcalz 联系我)
  • @caTS 我试过你的例子,但不幸的是我无法让它工作。你能告诉我一个有效的例子吗?
  • @jcalz 你的例子似乎有效,但我不太明白:D
  • @jcalz 我在我的应用程序代码中测试了您的解决方案,它可以正常工作。 :) 因此,如果您愿意,请随时写下答案/解释。 :)

标签: typescript typescript-typings typescript-generics


【解决方案1】:

对于 Handler&lt;Person&gt; 评估为强制执行 dataKey 属性和 handler 回调参数类型之间的相关性的类型,您非常需要它是一个 union type,其中一个成员用于 Person 的每个属性。它需要看起来像这样:

interface Person {
  name: string,
  age: number,
}

type PersonHandler = Handler<Person>;
/* type PersonHandler = {
    dataKey: "name";
    handler: (value: string) => void;
} | {
    dataKey: "age";
    handler: (value: number) => void;
} */

这样,Handler&lt;Person&gt; 要么是{dataKey: "name", handler: (value: string) =&gt; void 类型的值,要么是{dataKey: "age", handler: (value: number) =&gt; void 类型的值。

所以我们正在寻找一种方法来定义type Handler&lt;T extends object&gt; = ...,以便它生成合适的联合类型。 (请注意,interfaces 不能是联合类型,因此我已将 Handler 从接口更改为类型别名。)


这是一种方法:

type Handler<T> = { [K in keyof T]-?: {
  dataKey: K,
  handler: (value: T[K]) => void
} }[keyof T]

这就是所谓的分布式对象类型正如microsoft/TypeScript#47109 中创造的那样。分布式对象类型是 mapped type,它立即是 indexed into 以获得其属性类型的联合。您可以验证如果您有键的联合 type K = K1 | K2 | K3 | ... | KN,则分布式对象类型 {[P in K]: F&lt;P&gt;}[K] 的计算结果为 F&lt;K1&gt; | F&lt;K2&gt; | F&lt;K3&gt; | ... | F&lt;KN&gt;

在上面的 Handler&lt;T&gt; 定义中,我们遍历 T 的键(使用 -? mapping modifier 从属性中删除任何可能的可选性,以抑制输出中不需要的 undefined 类型),并且对于每个键K,我们计算对应的dataKey/handler对。然后我们用同一组键对其进行索引,以获得所有这些值的并集。

根据该定义,Handler&lt;Person&gt; 是所需的类型:

type PersonHandler = Handler<Person>;
/* type PersonHandler = {
    dataKey: "name";
    handler: (value: string) => void;
} | {
    dataKey: "age";
    handler: (value: number) => void;
} */

然后作业也按预期工作:

const handler: Handler<Person> = {
  dataKey: "name", handler: value => {
    console.log("Name:", value.toUpperCase());
  }
}

请注意,您不必 annotate valuestring。编译器能够推断出contextuallyvalue必须是string,因为Handler&lt;Person&gt;discriminated union,判别式dataKey"name"

Playground link to code

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-09-15
    • 2018-05-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-10
    • 2012-04-28
    • 1970-01-01
    相关资源
    最近更新 更多