【问题标题】:TypeScript generic factory function to return type based on instance argument without overloadingTypeScript 通用工厂函数基于实例参数返回类型而不重载
【发布时间】:2021-09-16 21:24:31
【问题描述】:

我想根据提供给工厂方法的实例参数创建实例。我能够使用重载编写以下代码。 如何使用 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

【问题讨论】:

    标签: typescript generics overloading typescript-generics


    【解决方案1】:

    您可以将pick() 的调用签名更改为generic 方法,其参数为泛型类型constrained 为您所需的不同输入类型的union,其返回类型为@987654324 @根据输入类型选择输出类型:

    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() 的主体内未指定,因此编译器不会也无法验证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

    【讨论】:

    • 非常感谢您的全面回答。实际上我有完整的 Cars 和 Humans 列表,在这种情况下我应该使用三元返回类型 T extends Car 吗?车库:动物?木:……:从来没有?还是有更好的方法?
    • 你可以写T extends Car ? Garage : T extends Animal ? Woods : T extends Human ? House : never,但是如果你用一些像this这样的编程解决方案可能会做得更好。
    • 非常感谢!这正是我想要的。
    猜你喜欢
    • 1970-01-01
    • 2019-06-07
    • 1970-01-01
    • 2017-06-01
    • 1970-01-01
    • 2023-03-30
    • 2021-02-07
    • 2021-01-23
    • 2022-11-04
    相关资源
    最近更新 更多