联合函数类型的推断
Inference of union function types
假设我有类似的架构:
type GetDog = () => { animal: string; bark: boolean };
const getDog: GetDog = () => ({ animal: 'dog', bark: true });
type GetCat = () => { animal: string; meow: boolean };
const getCat: GetCat = () => ({ animal: 'cat', meow: true });
type AnimalFactory =
| ((callback: GetDog) => ReturnType<typeof getDog>)
| ((callback: GetCat) => ReturnType<typeof getCat>);
const calmAnimalFactory: AnimalFactory = (callback) => {
// Some fancy stuff
return callback();
};
calmAnimalFactory
作为参数应该接受getDog
函数或getCat
函数,然后是returns对应的值。问题是我猜类型推断并没有像我想象的那样工作。 calmAnimalFactory
内的回调不会推断 GetDog | GetCat
的类型,而是 any
。我认为 Typescript 应该以 calmAnimalFactory(getDog)
应键入为
的方式确定 calmAnimalFactory
的类型
((callback: GetDog) => ReturnType<typeof getDog>)
和calmAnimalFactory(getCat)
应该输入为
((callback: GetCat) => ReturnType<typeof getCat>)
我希望它可以实现,但我不知道为什么它不能那样工作。
您可以使用泛型对此进行正确的输入。我们说 calmAnimalFactory
依赖于表示回调类型的泛型 T
。此 T
必须是一个接受零参数和 return 任何参数的函数。
现在我们可以说 calmAnimalFactory
的 callback
参数是 T
并且函数的 return 类型与 return 相同回调类型。
const calmAnimalFactory = <T extends () => any>(callback: T): ReturnType<T> => {
// Some fancy stuff
return callback();
};
const dog = calmAnimalFactory(getDog); // type: { animal: string; bark: boolean; }
const cat = calmAnimalFactory(getCat); // type { animal: string; meow: boolean; }
同一想法的一个略有不同的版本是让通用 T
指代所创建动物的类型。这使得要求回调的 return 必须适合某些基础 Animal
接口变得更容易。
interface Animal {
animal: string;
}
const calmAnimalFactory = <T extends Animal>(callback: () => T): T => {
// Some fancy stuff
return callback();
};
cat
和 dog
的类型仍然相同。
这里发生了一些事情。首先,我认为如果我必须一直输入 typeof getXXX
或 ReturnType<typeof getXXX>
,我会发疯,所以我将定义新的 Dog
和 Cat
接口,对应什么getDog()
和 getCat()
return,并专门使用它们:
interface Dog extends ReturnType<GetDog> { };
interface Cat extends ReturnType<GetCat> { }
您可以并且可能应该首先定义这些 Dog
和 Cat
类型,然后根据它们编写 getCat()
和 getDog()
:
interface Dog {
animal: string;
bark: boolean;
}
type GetDog = () => Dog;
const getDog: GetDog = () => ({ animal: 'dog', bark: true });
interface Cat {
animal: string;
meow: boolean;
}
type GetCat = () => Cat;
const getCat: GetCat = () => ({ animal: 'cat', meow: true });
但任何一种方法都可以。
下一步,如果你想要一个AnimalFactory
到return一个Cat
,当你传递一个GetCat
参数时,和 当你向它传递一个 GetDog
参数时,你希望它 return 一个 Dog
,然后你想使用一个 intersection (&
) and not a union (|
)。如果你使用工会,你是说动物工厂将 变成 GetCat
变成 Cat
, 或 它将把 GetDog
变成 Dog
,但不一定是两者。所以我们要交集在这里:
type AnimalFactory =
((callback: GetDog) => Dog)
& ((callback: GetCat) => Cat);
接下来,编译器无法很好地推断未注释和未断言的函数实现是否符合这样的签名交集。这样的推理等同于能够正确地进行类型检查 overloaded functions, which the compiler simply cannot do (see this comment in microsoft/TypeScript#35338 例如)。
因此,如果您只是尝试根据上下文键入 callback
参数,编译器将失败,并最终隐式使用 any
,如您所见。
最好使用一个 generic 函数实现,它声称能够将 () => T
类型的 callback
用于泛型 T extends Cat | Dog
,然后生成一个T
。这是编译器可以类型检查的东西:
const calmAnimalFactory: AnimalFactory =
<T extends Cat | Dog>(callback: () => T) => {
return callback();
};
编译器能够验证 <T extends Cat | Dog>(callback: () => T) => T
类型的函数是否可分配给 AnimalFactory
的两个调用签名,因此编译没有错误。
事实上,根据您的用例,您可能希望通过消除对 Cat
和 Dog
的依赖,使 AnimalFactory
尽可能通用,并提出一些基本类型,如 Animal
,例如:
interface Animal {
animal: string;
}
type UniversalAnimalFactory = <T extends Animal>(callback: () => T) => T;
const universalAnimalFactory: UniversalAnimalFactory = callback => callback();
UniversalAnimalFactory
可以做 AnimalFactory
可以做的任何事情(GetCat
进,Cat
出;GetDog
进,Dog
out) 但它还可以为您尚未想到的动物做任何其他事情:
const getFish = () => ({ animal: "fish", swim: true });
universalAnimalFactory(getFish).swim // works
假设我有类似的架构:
type GetDog = () => { animal: string; bark: boolean };
const getDog: GetDog = () => ({ animal: 'dog', bark: true });
type GetCat = () => { animal: string; meow: boolean };
const getCat: GetCat = () => ({ animal: 'cat', meow: true });
type AnimalFactory =
| ((callback: GetDog) => ReturnType<typeof getDog>)
| ((callback: GetCat) => ReturnType<typeof getCat>);
const calmAnimalFactory: AnimalFactory = (callback) => {
// Some fancy stuff
return callback();
};
calmAnimalFactory
作为参数应该接受getDog
函数或getCat
函数,然后是returns对应的值。问题是我猜类型推断并没有像我想象的那样工作。 calmAnimalFactory
内的回调不会推断 GetDog | GetCat
的类型,而是 any
。我认为 Typescript 应该以 calmAnimalFactory(getDog)
应键入为
calmAnimalFactory
的类型
((callback: GetDog) => ReturnType<typeof getDog>)
和calmAnimalFactory(getCat)
应该输入为
((callback: GetCat) => ReturnType<typeof getCat>)
我希望它可以实现,但我不知道为什么它不能那样工作。
您可以使用泛型对此进行正确的输入。我们说 calmAnimalFactory
依赖于表示回调类型的泛型 T
。此 T
必须是一个接受零参数和 return 任何参数的函数。
现在我们可以说 calmAnimalFactory
的 callback
参数是 T
并且函数的 return 类型与 return 相同回调类型。
const calmAnimalFactory = <T extends () => any>(callback: T): ReturnType<T> => {
// Some fancy stuff
return callback();
};
const dog = calmAnimalFactory(getDog); // type: { animal: string; bark: boolean; }
const cat = calmAnimalFactory(getCat); // type { animal: string; meow: boolean; }
同一想法的一个略有不同的版本是让通用 T
指代所创建动物的类型。这使得要求回调的 return 必须适合某些基础 Animal
接口变得更容易。
interface Animal {
animal: string;
}
const calmAnimalFactory = <T extends Animal>(callback: () => T): T => {
// Some fancy stuff
return callback();
};
cat
和 dog
的类型仍然相同。
这里发生了一些事情。首先,我认为如果我必须一直输入 typeof getXXX
或 ReturnType<typeof getXXX>
,我会发疯,所以我将定义新的 Dog
和 Cat
接口,对应什么getDog()
和 getCat()
return,并专门使用它们:
interface Dog extends ReturnType<GetDog> { };
interface Cat extends ReturnType<GetCat> { }
您可以并且可能应该首先定义这些 Dog
和 Cat
类型,然后根据它们编写 getCat()
和 getDog()
:
interface Dog {
animal: string;
bark: boolean;
}
type GetDog = () => Dog;
const getDog: GetDog = () => ({ animal: 'dog', bark: true });
interface Cat {
animal: string;
meow: boolean;
}
type GetCat = () => Cat;
const getCat: GetCat = () => ({ animal: 'cat', meow: true });
但任何一种方法都可以。
下一步,如果你想要一个AnimalFactory
到return一个Cat
,当你传递一个GetCat
参数时,和 当你向它传递一个 GetDog
参数时,你希望它 return 一个 Dog
,然后你想使用一个 intersection (&
) and not a union (|
)。如果你使用工会,你是说动物工厂将 变成 GetCat
变成 Cat
, 或 它将把 GetDog
变成 Dog
,但不一定是两者。所以我们要交集在这里:
type AnimalFactory =
((callback: GetDog) => Dog)
& ((callback: GetCat) => Cat);
接下来,编译器无法很好地推断未注释和未断言的函数实现是否符合这样的签名交集。这样的推理等同于能够正确地进行类型检查 overloaded functions, which the compiler simply cannot do (see this comment in microsoft/TypeScript#35338 例如)。
因此,如果您只是尝试根据上下文键入 callback
参数,编译器将失败,并最终隐式使用 any
,如您所见。
最好使用一个 generic 函数实现,它声称能够将 () => T
类型的 callback
用于泛型 T extends Cat | Dog
,然后生成一个T
。这是编译器可以类型检查的东西:
const calmAnimalFactory: AnimalFactory =
<T extends Cat | Dog>(callback: () => T) => {
return callback();
};
编译器能够验证 <T extends Cat | Dog>(callback: () => T) => T
类型的函数是否可分配给 AnimalFactory
的两个调用签名,因此编译没有错误。
事实上,根据您的用例,您可能希望通过消除对 Cat
和 Dog
的依赖,使 AnimalFactory
尽可能通用,并提出一些基本类型,如 Animal
,例如:
interface Animal {
animal: string;
}
type UniversalAnimalFactory = <T extends Animal>(callback: () => T) => T;
const universalAnimalFactory: UniversalAnimalFactory = callback => callback();
UniversalAnimalFactory
可以做 AnimalFactory
可以做的任何事情(GetCat
进,Cat
出;GetDog
进,Dog
out) 但它还可以为您尚未想到的动物做任何其他事情:
const getFish = () => ({ animal: "fish", swim: true });
universalAnimalFactory(getFish).swim // works