[英]Generic method's typing with generic type as a parameter does not work properly
[英]TypeScript: Typing generic interface's method parameter by reading a value from the generic type
我想定义一个能够处理任何Data
类型的通用接口。 该接口有一个dataKey
属性,它的值只是一个keyof Data
。 它还有一个 handler function,它的参数类型应该和使用dataKey
从Data
读取值的类型相同。 应该是这样的,但这不起作用,因为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.