繁体   English   中英

TypeScript:通过读取泛型类型的值来键入泛型接口的方法参数

[英]TypeScript: Typing generic interface's method parameter by reading a value from the generic type

我想定义一个能够处理任何Data类型的通用接口。 该接口有一个dataKey属性,它的值只是一个keyof Data 它还有一个 handler function,它的参数类型应该和使用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);

要使Handler<Person>评估为强制执行dataKey属性和handler回调参数类型之间相关性的类型,您非常需要它是一个联合类型,其中一个成员用于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<Person>要么是{dataKey: "name", handler: (value: string) => void类型的值,要么是{dataKey: "age", handler: (value: number) => void

所以我们正在寻找一种方法来定义type Handler<T extends object> =...以便它生成适当的联合类型。 (注意interface不能是联合类型,所以我将Handler从接口更改为类型别名。)


这是一种方法:

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

这就是microsoft/TypeScript#47109中创造的所谓的分布式 object 类型 分布式 object 类型是一种映射类型,它会立即被索引以获取其属性类型的联合。 您可以验证是否有键的并集type K = K1 | K2 | K3 |... | KN type K = K1 | K2 | K3 |... | KN type K = K1 | K2 | K3 |... | KN ,则分配式 object 类型{[P in K]: F<P>}[K]的计算结果为F<K1> | F<K2> | F<K3> |... | F<KN> F<K1> | F<K2> | F<K3> |... | F<KN> F<K1> | F<K2> | F<K3> |... | F<KN>

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

根据该定义, Handler<Person>是所需的类型:

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());
  }
}

请注意,您不必注释value是一个string 编译器能够根据上下文推断value必须是string ,因为Handler<Person>是一个可区分的 union ,而可区分的dataKey"name"

游乐场代码链接

暂无
暂无

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

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