通用类型未解析为联合

时间:2019-02-27 23:22:41

标签: typescript

以下代码错误:


class Base { }

class Child1 extends Base {
  child1Fn() {}
  static deserialize(bytes: Uint8Array): Child1 {
    return new Child1();
  }
}

class Child2 extends Base {
  child2Fn() {}
  static deserialize(bytes: Uint8Array): Child2 {
    return new Child2();
  }
}

const childMap = {
  "child1": Child1.deserialize,
  "child2": Child2.deserialize
}

function deserialize<T>(
  data: { name: string, bytes: Uint8Array },
  deserializeMap: Record<string, (bytes: Uint8Array) => T>
): T {
  const deserializeFn = deserializeMap[data.name];
  if (deserializeFn) {
    return deserializeFn(data.bytes)
  }
}

function deserializeChildMap(data: { name: string, bytes: Uint8Array }) {
  return deserialize(data, childMap)
}

Playground Link

我最终遇到错误:

Argument of type '{ "child1": (bytes: Uint8Array) => Child1; "child2": (bytes: Uint8Array) => Child2; }' is not assignable to parameter of type 'Record<string, (bytes: Uint8Array) => Child1>'.
  Property '"child2"' is incompatible with index signature.
    Type '(bytes: Uint8Array) => Child2' is not assignable to type '(bytes: Uint8Array) => Child1'.
      Property 'child1Fn' is missing in type 'Child2' but required in type 'Child1'.

它看起来将类型T解析为childMap(Child1)中的第一个返回值。理想情况下,我希望T解析为Child1 | Child2。有没有办法做到这一点?

2 个答案:

答案 0 :(得分:2)

可以做到,但是还需要一些通用类型参数:

class Base { }

class Child1 extends Base {
  child1Fn() { }
  static deserialize(bytes: Uint8Array): Child1 {
    return new Child1();
  }
}

class Child2 extends Base {
  child2Fn() { }
  static deserialize(bytes: Uint8Array): Child2 {
    return new Child2();
  }
}

const childMap = {
  "child1": Child1.deserialize,
  "child2": Child2.deserialize
}

function deserialize<TName extends string, TMap extends Record<TName, (bytes: Uint8Array) => any>>(
  data: { name: TName, bytes: Uint8Array },
  deserializeMap: TMap
): ReturnType<TMap[TName]> {
  const deserializeFn = deserializeMap[data.name];
  if (deserializeFn) {
    return deserializeFn(data.bytes)
  }
}

function deserializeChildMap<TName extends keyof typeof childMap>(data: { name: TName, bytes: Uint8Array }): ReturnType<typeof childMap[TName]> {
  return deserialize(data, childMap)
}

let c1 = deserializeChildMap({ name: "child1", bytes: null!}) // child1 
let c2 = deserializeChildMap({ name: "child2", bytes: null!}) // child2 

答案 1 :(得分:0)

Titian's answer非常好。为了完整起见,这是我的目的:

function deserialize<T>(
  data: { name: string, bytes: Uint8Array },
  deserializeMap: { [key in keyof T]: (bytes: Uint8Array) => T[key] }
): T[keyof T] {
  const deserializeFn = deserializeMap[data.name];
  if (deserializeFn) {
    return deserializeFn(data.bytes)
  }
}

deserializeChildMap的签名现在推断为:

function deserializeChildMap(data: { name: string; bytes: Uint8Array; }): Child1 | Child2