简体   繁体   中英

TypeScript return conditional type from factory function

I have a union type of objects that can under certain conditions be assigned a special behavior. This behavior depends on the type of object. Some of the objects are more special than others and can have more or different functions.

I want to create a factory function that takes an instance of one item of this union type and returns the right behavior class for this instance. It would look like this:


type A = {
  type: "A"
  name: string,
}
type B = {
  type: "B",
  name: string,
  age: number
}

type Things = A | B


type Base<T extends Things> = {
  f1(): string
}

type Special<T extends Things> = T extends B ? { f2(): number } : {}

type Behavior<T extends Things> = Base<T> & Special<T>


class BehaviorClassA implements Behavior<A> {
  constructor(private readonly instance: A) { }
  f1(): string {
    return "some A behavior for " + this.instance.name
  }
}

class BehaviorClassB implements Behavior<B> {
  constructor(private readonly instance: B) { }
  f1(): string {
    return "some B behavior for " + this.instance.name
  }
  f2(): number {
    return this.instance.age + 42
  }
}


function getBehaviorForThing<T extends Things>(thing: T): T extends B ? BehaviorClassB : BehaviorClassA {
  if (isA(thing)) {
    return new BehaviorClassA(thing) // Type 'BehaviorClassA' is not assignable to type 'T extends B ? BehaviorClassB : BehaviorClassA'.

  }
  if (isB(thing)) {
    return new BehaviorClassB(thing) // Type 'BehaviorClassB' is not assignable to type 'T extends B ? BehaviorClassB : BehaviorClassA'.
  }
  throw new Error("invalid thing")
}

function isA(t: Things): t is A {
  return t.type === "A"
}
function isB(t: Things): t is B {
  return t.type === "B"
}


// consumer:
const myB: B = { 
  type: "B",
  name: "John",
  age: 23
}
getBehaviorForThing(myB).f2()

However as you can see the factory function can not be typed correctly.

Does anyone have an idea what I would have to do to get it work as intended?

Function overload is a correct option, but you can do the followinf as well which is a "kind of" type guard:

type ReturnType<T> = T extends B? BehaviorClassB : BehaviorClassA
  
  function getBehaviorForThing<T extends Things>(thing: T): ReturnType<T> {
    if (isA(thing)) {
        const resu: BehaviorClassA = new BehaviorClassA(thing) 
      return resu  as ReturnType<T>
  
    }
    if (isB(thing)) {
      return new BehaviorClassB(thing) as ReturnType<T> 
    }
    throw new Error("invalid thing")
  }

 // consumer:
  const myB: B = { 
    type: "B",
    name: "John",
    age: 23
  }
  getBehaviorForThing(myB).f2() <-- is type BehaviorClassB

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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