[英]Can Typescript infer keys of a generic type inside conditional blocks?
我有一个 function 接受一个枚举值作为T
和一个泛型类型Data<T>
,它在两种数据类型之间进行选择。
我希望能够在应该使T
已知的条件中访问BarData
类型的属性。 但是,它仍然以联合类型读取data
。
代码按预期工作,但我必须改变什么才能摆脱 typescript 错误?
enum DataType { Foo, Bar }
interface FooData { someKey: string }
interface BarData extends FooData { otherKey: string }
type Data<T extends DataType> = T extends DataType.Foo ? FooData : BarData
function func<T extends DataType>(type: T, data: Data<T>): void {
const getter = <K extends keyof Data<T>>(key: K): Data<T>[K] => data[key]
if (type === DataType.Bar) {
data; // still inferred as Data<T>
console.log(data.otherKey) // error Property 'otherKey' does not exist on type 'FooData | BarData'.
console.log(getter('otherKey')) // error Argument of type 'string' is not assignable to parameter of type 'keyof Data<T>'.
}
}
您需要确保无效的 state 是不可表示的。 您可以使用 rest 参数而不是通用参数。
enum DataType { Foo = 'Foo', Bar = 'Bar' }
interface FooData { someKey: string }
interface BarData extends FooData { otherKey: string }
type MapStructure = {
[DataType.Foo]: FooData,
[DataType.Bar]: BarData
}
type Values<T> = T[keyof T]
type Tuple = {
[Prop in keyof MapStructure]: [type: Prop, data: MapStructure[Prop]]
}
// ---- > BE AWARE THAT IT WORKS ONLY IN T.S. 4.6 < -----
function func(...params: Values<Tuple>): void {
const [type, data] = params
const getter = <Data, Key extends keyof Data>(val: Data, key: Key) => val[key]
if (type === DataType.Bar) {
const foo = type
data; // BarData
console.log(data.otherKey) // ok
console.log(getter(data, 'otherKey')) // ok
console.log(getter(data, 'someKey')) // ok
}
}
MapStructure
- 仅用于映射具有有效 state 的键。
Values<Tuple>
- 创建允许的元组的联合。由于 rest 参数只不过是一个元组,它就像一个魅力。
关于getter
。 您应该在if
条件中定义它或将其分开 function。 所以,请随意将getter
从func
的 scope 中移出。
如果您想坚持使用 generics,就像在原始示例中一样,您应该将type
和data
作为一个数据结构的一部分,然后使用typeguard
enum DataType { Foo, Bar }
interface FooData { someKey: string }
interface BarData extends FooData { otherKey: string }
type Data<T extends DataType> = T extends DataType.Foo ? FooData : BarData
const isBar = (obj: { type: DataType, data: Data<DataType> }): obj is { type: DataType.Bar, data: BarData } => {
const { type, data } = obj;
return type === DataType.Bar && 'other' in data
}
function func<T extends DataType>(obj: { type: T, data: Data<T> }): void {
const getter = <K extends keyof Data<T>>(key: K): Data<T>[K] => obj.data[key]
if (isBar(obj)) {
obj.data // Data<T> & BarData
console.log(obj.data.otherKey) // ok
}
}
但是getter
的问题仍然存在,因为它依赖于未推断的obj.data
。 您要么需要移出func
scope 的getter
并为data
提供额外的参数,要么需要在conditional statement
移动getter
(不推荐)。
但是,您可以在 TS 操场上每晚切换到 TypeScript 并使用 object 类型作为参数:
enum DataType { Foo, Bar }
interface FooData { someKey: string }
interface BarData extends FooData { otherKey: string }
type Data = { type: DataType.Foo, data: FooData } | { type: DataType.Bar, data: BarData }
function func(obj: Data): void {
const { type, data } = obj;
const getter = <K extends keyof typeof data>(key: K): typeof data[K] => data[key]
if (type === DataType.Bar) {
data // BarData
console.log(obj.data.otherKey) // ok
}
}
getter
仍然无法以您期望的方式工作,因此我建议将其从func
中移出
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.