如何干净地编写具有不同回调重载参数的 TypeScript 函数
How to Cleanly Write a TypeScript Function With Different Callback Overload Parameters
这是 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 类型参数的重载?或者也许是编写其中任何一个的清洁工?基本上,该函数有条件地使用给定的 animal
调用给定的 func
。如果给定一只狗,则返回类型 T
。如果给定其他动物,则返回类型 Foo<T>
。
更新 1
我无法让@jcalz 版本工作,但我花了一段时间才意识到它与承诺有关,但我不确定如何解决这个问题。以下是我的 "ugly but it works" 方法和@jalz 的 "it's nice but it's broke" 方法:
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
输入,但会 return 不同类型,具体取决于其输入是否为 Dog
。这意味着我将声明 func
到以下重载类型:
type AnimalFunc<T> = {
(dog: Dog): T;
(animal: Animal): Foo<T>;
}
然后,函数 test
只是将它的 animal
输入传递给它的 func
输入,并且 return 返回它返回的任何类型。为了实现这一点,我会这样声明 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 :
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 :
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
和 return 一个 Promise<T>
, 或者 取一个 Animal
和 return 取一个 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
的实现签名与我认为的一样具体。它接受 animal
的任何类型的动物 A
,以及一个肯定会接受 animal
和 return 的函数 func
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!!
}
这会导致第一个重载声明出现问题,因为它从来没有 return Promise<T>
。
希望对您有所帮助。
这是 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 类型参数的重载?或者也许是编写其中任何一个的清洁工?基本上,该函数有条件地使用给定的 animal
调用给定的 func
。如果给定一只狗,则返回类型 T
。如果给定其他动物,则返回类型 Foo<T>
。
更新 1
我无法让@jcalz 版本工作,但我花了一段时间才意识到它与承诺有关,但我不确定如何解决这个问题。以下是我的 "ugly but it works" 方法和@jalz 的 "it's nice but it's broke" 方法:
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 givenanimal
. If given a dog, typeT
is returned. If given some other animal, a typeFoo<T>
is returned.
表示参数 func
接受所有 Animal
输入,但会 return 不同类型,具体取决于其输入是否为 Dog
。这意味着我将声明 func
到以下重载类型:
type AnimalFunc<T> = {
(dog: Dog): T;
(animal: Animal): Foo<T>;
}
然后,函数 test
只是将它的 animal
输入传递给它的 func
输入,并且 return 返回它返回的任何类型。为了实现这一点,我会这样声明 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
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
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
和 return 一个 Promise<T>
, 或者 取一个 Animal
和 return 取一个 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
的实现签名与我认为的一样具体。它接受 animal
的任何类型的动物 A
,以及一个肯定会接受 animal
和 return 的函数 func
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!!
}
这会导致第一个重载声明出现问题,因为它从来没有 return Promise<T>
。
希望对您有所帮助。