TypeScript 泛型工厂函数基于实例参数 return 类型而不重载

TypeScript generic factory function to return type based on instance argument without overloading

我想根据提供给工厂方法的实例参数创建实例。我能够使用重载编写以下代码。 如何使用TypeScript泛型来表达?

class Car {
    brand = 'Skoda';
}

class Human {
    name = 'Jan';
}

class Garage {
    lots = 5;
}

class House {
    floors = 2;
}

class NotAllowed {
    anything = 'anything';
}

class Selection {

    pick(what: Car) : Garage;
    pick(what: Human) : House;
    pick(what: any) : any {
        if (what instanceof Car) {
            return new Garage();
        } else if (what instanceof Human) {
            return new House();
        } else {
            throw Error('Not supported')
        }
    }

}

const selection = new Selection();
selection.pick(new Car()).lots; // compiler successfully autocompletes 
selection.pick(new Human()).floors // compiler successfully autocompletes 
selection.pick(new NotAllowed()); // compiler error

您可以将 pick() 的调用签名更改为 generic method whose parameter is of a generic type constrained to the union of your different desired input types, and whose return type is a conditional type,它根据输入类型选择输出类型:

declare class Selection {
  pick<T extends Car | Human>(what: T): T extends Car ? Garage : House;
}

这与您的示例调用的行为相同:

const selection = new Selection();
selection.pick(new Car()).lots; // compiler successfully autocompletes 
selection.pick(new Human()).floors // compiler successfully autocompletes 
selection.pick(new NotAllowed()); // compiler error

很遗憾,您的实现不会被视为类型安全; T 类型在 pick() 的主体内将是 unspecified,因此编译器不会也无法验证 T 是否可分配给 Car 即使 what instanceof Car 为真:

pick<T extends Car | Human>(what: T): T extends Car ? Garage : House {
  if (what instanceof Car) {
    return new Garage(); // error!
    // Type 'Garage' is not assignable to type 'T extends Car ? Garage : House'
  } else if (what instanceof Human) {
    return new House(); // error!
    // Type 'House' is not assignable to type 'T extends Car ? Garage : House'.
  } else {
    throw Error('Not supported')
  }
}

我不会继续讨论为什么会发生这种情况,而是直接将您引导至 microsoft/TypeScript#33912,相关开放功能请求,以改进语言支持以实现返回条件类型的泛型函数。

现在你需要做一些像 type assertion:

pick<T extends Car | Human>(what: T): T extends Car ? Garage : House {
  if (what instanceof Car) {
    return new Garage() as T extends Car ? Garage : House;
  } else if (what instanceof Human) {
    return new House() as T extends Car ? Garage : House;
  } else {
    throw Error('Not supported')
  }
}

或等效地,单个呼号overload:

pick<T extends Car | Human>(what: T): T extends Car ? Garage : House;
pick(what: Car | Human) {
  if (what instanceof Car) {
    return new Garage();
  } else if (what instanceof Human) {
    return new House();
  } else {
    throw Error('Not supported')
  }
}

我知道你说过你不想重载,但这通常是我的解决方案,因为在任何地方添加类型断言都比较混乱和乏味。

Playground link to code