typescript - 如何使用不同的回调重载参数干净地编写 TypeScript 函数

标签 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版本工作,但我花了一段时间才意识到它与 promise 有关,但我不确定如何解决这个问题。以下是我的“丑陋但有效”方法和@jalz 的“它很好但很糟糕”方法:

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); });

最佳答案

我明白了

the function conditionally calls the given func with the given animal. If given a dog, type T is returned. If given some other animal, a type Foo<T> is returned.

表示参数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 :

This works for the definition, but not call sites. If I pass in a dog as the first parameter, my function must accept a dog and return an T, else it must accept an animal and return a Foo, At the call sites, it complains the the function isn't returning the other type (T or 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 :

This works for the definition, but not call sites. If I pass in a dog as the first parameter, my function must accept a dog and return an T, else it must accept an animal and return a Foo, At the call sites, it complains the the function isn't returning the other type (T or Foo)

好吧,我认为这与 Promise 没有太大关系。 s。看起来你想要 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那肯定会接受animal并返回 Promise<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>

我希望这有帮助。

关于typescript - 如何使用不同的回调重载参数干净地编写 TypeScript 函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45087264/

相关文章:

typescript - 如何在 TypeScript 中迭代字符串索引数组?

angular - 使用angular4下载pdf格式的网页

typescript - 更新到 TypeScript 2.4.1 和 Rxjs 5.4.2 后 Observable.forkJoin() TS2322 错误

reactjs - 选择后聚焦文本输入

javascript - 期望中间件函数调用下一个函数或发送 http 400 代码

angular - ng2-select 中的数据绑定(bind)失败

TypeScript 字符串枚举 - "Type ... is not assignable to type ..."

typescript - 如何在 Typescript 中编写严格要求其参数的函数类型

typescript - 构造函数中的公共(public)其余参数