如何使用不同的回调过载参数清晰地编写TypeScript函数

时间:2017-07-13 17:21:00

标签: typescript typescript2.4

这是this question

的扩展

鉴于此代码:

class Animal {
    a: string;
}

class Dog extends Animal {
    b: string;
}

class Foo<T>{}

function test<T,A extends Dog>(animal:A, func: (p: A) => T): T;
function test<T,A extends Animal>(animal:A, func: (p: A) => Foo<T>): Foo<T>;
function test<T,A extends Animal>(animal:A, func: (p: A) => T|Foo<T>): T|Foo<T> {
    return func(animal);
}

是否有更简洁的方法来编写不需要A类型参数的重载?或者也许是写作任何一个的清洁工?基本上,该函数有条件地使用给定的func调用给定的animal。如果给狗,则返回类型T。如果给出其他动物,则返回类型Foo<T>

更新1

我无法让@jcalz版本工作,但我花了一段时间才意识到这与承诺有关,但我不知道如何解决这个问题。下面是我的&#34;丑陋但它有效&#34;方法论,@ jalz&#34;它很好但是它已经破坏了#34;方法:

class Animal {
    a: string;
}

class Dog extends Animal {
    b: string;
}

class Foo<T>{ }

function test<T, A extends Dog>(animal: A, func: (p: A) => Promise<T>): Promise<T>;
function test<T, A extends Animal>(animal: A, func: (p: A) => Promise<Foo<T>>): Promise<Foo<T>>;
function test<T, A extends Animal>(animal: A, func: (p: A) => Promise<T> | Promise<Foo<T>>): Promise<T | Foo<T>> {
    return func(animal);
}

const foo: Promise<Foo<string>> = test(new Animal(), (a) => { return Promise.resolve(new Foo<string>()); });
const other: Promise<string> = test(new Dog(), (d) => { return Promise.resolve(d.b); });

type AnimalFunc<T> = {
    (dog: Dog): Promise<T>;
    (animal: Animal): Promise<Foo<T>>;
}

function test2<T>(dog: Dog, func: AnimalFunc<T>): Promise<T>;
function test2<T>(animal: Animal, func: AnimalFunc<T>): Promise<Foo<T>>;
function test2<T>(animal: Animal, func: AnimalFunc<T>): Promise<T | Foo<T>> {
    return func(animal);
}

const foo2: Promise<Foo<string>>  = test2(new Animal(),
    (a) => {
        return Promise.resolve(new Foo<string>());
    }); // Errors: TS2345   Argument of type '(a: any) => Promise<Foo<string>>' is not assignable to parameter of type 'AnimalFunc<string>'.
        // Type 'Promise<Foo<string>>' is not assignable to type 'Promise<string>'.
        // Type 'Foo<string>' is not assignable to type 'string'.TypeScript Virtual Projects    C: \_Dev\CRM\WebResources\webresources\new_\scripts\Payment.ts  498 Active

const other2: Promise<string>  = test2(new Dog(), (d) => { return Promise.resolve(d.b); });

1 个答案:

答案 0 :(得分:1)

我理解

  

该函数有条件地使用给定的func调用给定的animal。如果给狗,则返回类型T。如果给出其他动物,则返回类型Foo<T>

表示参数func接受所有Animal输入,但会返回不同类型,具体取决于其输入是否为Dog。这意味着我会将func声明为以下重载类型:

type AnimalFunc<T> = {
    (dog: Dog): T;
    (animal: Animal): Foo<T>;
}

然后,函数test只将其animal输入传递给它的func输入,并返回它返回的任何类型。要实现这一点,我会像这样声明test

function test<T>(dog: Dog, func: AnimalFunc<T>): T;
function test<T>(animal: Animal, func: AnimalFunc<T>): Foo<T>;
function test<T>(animal: Animal, func: AnimalFunc<T>): T | Foo<T> {
    return func(animal);
}

希望有所帮助。

更新1

@daryl said

  

这适用于定义,但不适用于呼叫站点。如果我传入一只狗作为第一个参数,我的函数必须接受一个狗并返回一个T,否则它必须接受一个动物并返回一个Foo,在调用站点,它会抱怨该函数没有返回另一个类型(T或Foo)

在不知道所有用例的情况下,我无法确定最佳定义是什么。如果你真的有AnimalFunc<T>类型的函数,它应该可以工作:

function func1(dog: Dog): string;    
function func1(animal: Animal): Foo<string>;
function func1(animal: Animal): string | Foo<string> {
  if (animal instanceof Dog) {
    return "woof";
  }
  return new Foo<string>();
};

var dog: Dog = new Dog();
var cat: Animal = new Animal();
var dogTest: string = test(dog, func1);
var catTest: Foo<string> = test(cat, func1);

如果您尝试传递其他类型的功能,请说明用例。感谢。

更新2

@daryl said

  

这适用于定义,但不适用于呼叫站点。如果我传入一只狗作为第一个参数,我的函数必须接受一个狗并返回一个T,否则它必须接受一个动物并返回一个Foo,在调用站点,它会抱怨该函数没有返回另一个类型(T或Foo)

好的,我不认为这与Promise有很大关系。看起来您希望func 获取Dog并返回Promise<T>获取Animal并返回Promise<Foo<T>>,但不一定都是。也就是说,特定func可能只需要Dog,并且不会接受Cat。这不是我最初的理解。

对于这种情况,那么我想说你想做:

function test3<T>(dog: Dog, func: (dog: Dog) => Promise<T>): Promise<T>;
function test3<T>(animal: Animal, func: (animal: Animal) => Promise<Foo<T>>): Promise<Foo<T>>;
function test3<T, A extends Animal>(animal: A, func: (animal: A) => Promise<T> | Promise<Foo<T>>): Promise<T> | Promise<Foo<T>> {
  return func(animal);
}

请注意,test3(前两行)的声明是为了调用者的利益而输入的,而实现(第三行)的输入是为了实施者。如果你所关心的只是为那些呼叫test3的人提供类型安全但在你的实现中足够安全,你不需要TS为你验证类型,那么你可以把它实现为:

function test3<T>(dog: Dog, func: (dog: Dog) => Promise<T>): Promise<T>;
function test3<T>(animal: Animal, func: (animal: Animal) => Promise<Foo<T>>): Promise<Foo<T>>;
function test3(animal: any, func: any): any {
  return func(animal); // fine, but even return animal(func) would be accepted here, to disastrous results at runtime
}

具有通用A的实现签名与我认为可以获得的具体相同。它接受A的任何类型的动物animal,以及肯定会接受func并返回animalPromise<T>的函数Promise<Foo<T>> }。这对于调用func(animal)来说足够安全,但你仍然可以通过实现类似

的实现来欺骗类型检查器。
function test3<T, A extends Animal>(animal: A, func: (animal: A) => Promise<T> | Promise<Foo<T>>): Promise<T> | Promise<Foo<T>> {
  return Promise.resolve(new Foo<T>()); // no error!!
}

会导致第一个重载声明出现问题,因为它不会返回Promise<T>

我希望这有帮助。