繁体   English   中英

Typescript 可以在条件块内推断出泛型类型的键吗?

[英]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。 所以,请随意将getterfunc的 scope 中移出。

如果您想坚持使用 generics,就像在原始示例中一样,您应该将typedata作为一个数据结构的一部分,然后使用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.

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