为什么我无法从实现接口的 class 推断类型参数?
Why am i unable to infer type parameters from a class that implements an interface?
背景
我有一个声明单个方法的接口,其 return 类型取决于提供给接口的类型参数。
interface Command<T, U = void> {
execute(input: T): [U] extends [void] ? T | Observable<T>: T | U | Observable<T|U>;
}
然后我有一个实用程序类型,它应该 return 类型参数 T
和 U
用于提供的类型。
type GetCommandParamTypes<T extends Command<unknown, unknown>> =
T extends Command<infer IO, infer A> ?
[IO, A] :
never;
问题
虽然 GetCommandParamTypes
在提供 Command
接口时按预期工作,但我正在努力调试为什么我在提供 [=111= 时没有得到预期的结果] 实现该接口。
type type1 = GetCommandParamTypes<Command<string>>;
// [string, void]
type type2 = GetCommandParamTypes<Command<string, string>>;
// [string, string]
type type3 = GetCommandParamTypes<Command<string | number, string>>;
// [string | number, string]
type type4 = GetCommandParamTypes<Command<string | number, Function>>;
// [string | number, Function]
type type5 = GetCommandParamTypes<Command<string, number | Function>>;
// [string, number | Function]
type type6 = GetCommandParamTypes<TypeACommand>; // expected [string, void]
// [string, string]
type type7 = GetCommandParamTypes<TypeBCommand>; // expected [string, number | Function]
// [string | number | Function, string | number | Function]
type type8 = GetCommandParamTypes<TypeCCommand>; // expected [string, void]
// [string, string | Observable<string>]
type type9 = GetCommandParamTypes<MixedTypeCommand>; // expected [string | number, boolean | Function]
// [string | number | boolean | Function, string | number | boolean | Function]
type type10 = GetCommandParamTypes<MixedTypeObservableCommand>; // expected [string | number, boolean | Function]
// [string | number | boolean | Function, string | number | boolean | Function | Observable<string | number | boolean | Function>]
问题
是否可以从实现接口的 Class 推断类型参数,或者我的实现有问题?
有人可以解释当检查 Class 以推断类型参数时会发生什么,或者向我指出可能有助于解释它的任何资源的方向吗?
更新 1
如果我从命令 return 类型中删除条件类型(这不是所需的行为,因为第二个类型参数是可选的,我不希望 void
在 return 如果未提供,请键入)它似乎更接近我的预期。或者至少是更可行的东西。
interface Command<T, U = void> {
execute(input: T): T | U | Observable<T | U>;
}
乍一看似乎 execute
方法 input
类型正在 returned 用于 infer IO
而 return 类型正在 return编辑 infer A
.
但对于具有以下签名的 TypeACommand
:
execute(input: string): string | Observable<string>
[string, string]
是 returned,而不是 [string, string | Observable<string>]
其中 TypeCCommand
具有以下签名:
execute(input: string): Observable<string>
[string, string | Observable<string>]
实际上是 returned 而不是 [string, Observable<string>]
和 MixedTypeObservableCommand
也只有 Observable
作为 return 类型也 returns Observable<...>
和 Observable
本身。
所以:
execute(input: string | number): Observable<string | number | Function | boolean>
变为:
[string | number, string | number | boolean | Function | Observable<string | number | boolean | Function>]
更新 2
这是我之前创建的一个工作示例的 link,在将 Observable
添加到 Command
接口的 return 类型之前。
至少在 returned 的 infer IO
和 infer A
版本中似乎是一致的,其中 IO
是 execute
方法输入类型和 A
是 execute
方法 return 类型。
然后我可以使用 Exclude
来计算与 Command
接口相关的相应类型
之所以会发生这种情况,是因为当检查 GetCommandParamTypes
中的 T
是 Command
的一个实例时,它实际上并没有检查您提供的 class实现接口。 Typescript 具有结构性而非名义类型,因此它所做的是比较 Command<..., ...>
和 T
的签名。类型 TypeACommand
有签名
type TypeOfACommandObject = {
execute(input: string): string | Observable<string>
}
没有任何迹象表明它实现了 Command
,它只是一个对象,它有一个带有特定参数和 return 类型的方法 execute
。如果现在将此对象传递给 GetCommandParamTypes
,您会发现完全相同的问题:您可能希望 return 类型为 [string, void]
,但实际上是 [string, string]
。而且也不矛盾,因为Command<string, string>
有签名
{
execute(input: string): string | string | Observable<string | string>
}
这与 Command<string, void>
的签名没有区别。所以你不能那么容易地输入它。
当您尝试 GetCommandParamType<Command<string, void>>
时它会正常工作的原因,即使 Command<string, void>
的签名如上所述并且您可能希望它也被错误地推断出来,是因为typescript 的编译器实现。我假设当检查类型 T
是否是 Command
的实例时,它首先检查 T
是否是 Command
。在这种情况下,它 是 字面意思和 Command
的实例,因此它只是跳过比较结构并产生正确的类型参数。然而 TypeOfACommandObject
和 TypeACommand
不是 Command
的直接实例,即使后者实现了它,所以这个快捷方式失败了。不过这是一个猜测,我不熟悉打字稿的实现。
说到修复它,如果不重构 execute
的 return 类型,我真的想不出什么好方法,这样 T
和 U
就不会了合并。我可以想到品牌类型,例如使用 unique symbol
类型向 return 类型的命令添加特殊的唯一签名,但是在实现 Command
和在外部使用它时都很难同时使用.您可能应该尝试以某种方式将 T
和 U
分开
更新:
在更新的示例中,由于 GetCommandTypeParams
的不同定义,它可以工作
type ReplaceNever<T, U> = [T] extends [never] ? U : T;
type GetCommandParamTypes<T extends Command<unknown, unknown>> =
T extends Command<infer IO, infer A> ?
[IO, ReplaceNever<Exclude<A, IO>, void>] :
never;
如果你在第一个例子中坚持同样的定义,你会发现它也几乎按预期工作,唯一的问题是最后的 class 和 Observables
。所以你需要稍微调整一下
type GetCommandParamTypes<T extends Command<unknown, unknown>> =
T extends Command<infer IO, infer A> ?
[IO, ReplaceNever<A extends Observable<infer U> ? Exclude<U, IO> : Exclude<A, IO>, void>] :
never;
这个解决方案有一些奇怪的地方,比如你不能完全从实现 Command<string | number, string | Function>
的 classes 中提取类型,但是如果你能接受它就没问题
背景
我有一个声明单个方法的接口,其 return 类型取决于提供给接口的类型参数。
interface Command<T, U = void> {
execute(input: T): [U] extends [void] ? T | Observable<T>: T | U | Observable<T|U>;
}
然后我有一个实用程序类型,它应该 return 类型参数 T
和 U
用于提供的类型。
type GetCommandParamTypes<T extends Command<unknown, unknown>> =
T extends Command<infer IO, infer A> ?
[IO, A] :
never;
问题
虽然 GetCommandParamTypes
在提供 Command
接口时按预期工作,但我正在努力调试为什么我在提供 [=111= 时没有得到预期的结果] 实现该接口。
type type1 = GetCommandParamTypes<Command<string>>;
// [string, void]
type type2 = GetCommandParamTypes<Command<string, string>>;
// [string, string]
type type3 = GetCommandParamTypes<Command<string | number, string>>;
// [string | number, string]
type type4 = GetCommandParamTypes<Command<string | number, Function>>;
// [string | number, Function]
type type5 = GetCommandParamTypes<Command<string, number | Function>>;
// [string, number | Function]
type type6 = GetCommandParamTypes<TypeACommand>; // expected [string, void]
// [string, string]
type type7 = GetCommandParamTypes<TypeBCommand>; // expected [string, number | Function]
// [string | number | Function, string | number | Function]
type type8 = GetCommandParamTypes<TypeCCommand>; // expected [string, void]
// [string, string | Observable<string>]
type type9 = GetCommandParamTypes<MixedTypeCommand>; // expected [string | number, boolean | Function]
// [string | number | boolean | Function, string | number | boolean | Function]
type type10 = GetCommandParamTypes<MixedTypeObservableCommand>; // expected [string | number, boolean | Function]
// [string | number | boolean | Function, string | number | boolean | Function | Observable<string | number | boolean | Function>]
问题
是否可以从实现接口的 Class 推断类型参数,或者我的实现有问题?
有人可以解释当检查 Class 以推断类型参数时会发生什么,或者向我指出可能有助于解释它的任何资源的方向吗?
更新 1
如果我从命令 return 类型中删除条件类型(这不是所需的行为,因为第二个类型参数是可选的,我不希望 void
在 return 如果未提供,请键入)它似乎更接近我的预期。或者至少是更可行的东西。
interface Command<T, U = void> {
execute(input: T): T | U | Observable<T | U>;
}
乍一看似乎 execute
方法 input
类型正在 returned 用于 infer IO
而 return 类型正在 return编辑 infer A
.
但对于具有以下签名的 TypeACommand
:
execute(input: string): string | Observable<string>
[string, string]
是 returned,而不是 [string, string | Observable<string>]
其中 TypeCCommand
具有以下签名:
execute(input: string): Observable<string>
[string, string | Observable<string>]
实际上是 returned 而不是 [string, Observable<string>]
和 MixedTypeObservableCommand
也只有 Observable
作为 return 类型也 returns Observable<...>
和 Observable
本身。
所以:
execute(input: string | number): Observable<string | number | Function | boolean>
变为:
[string | number, string | number | boolean | Function | Observable<string | number | boolean | Function>]
更新 2
这是我之前创建的一个工作示例的 link,在将 Observable
添加到 Command
接口的 return 类型之前。
至少在 returned 的 infer IO
和 infer A
版本中似乎是一致的,其中 IO
是 execute
方法输入类型和 A
是 execute
方法 return 类型。
然后我可以使用 Exclude
来计算与 Command
接口相关的相应类型
之所以会发生这种情况,是因为当检查 GetCommandParamTypes
中的 T
是 Command
的一个实例时,它实际上并没有检查您提供的 class实现接口。 Typescript 具有结构性而非名义类型,因此它所做的是比较 Command<..., ...>
和 T
的签名。类型 TypeACommand
有签名
type TypeOfACommandObject = {
execute(input: string): string | Observable<string>
}
没有任何迹象表明它实现了 Command
,它只是一个对象,它有一个带有特定参数和 return 类型的方法 execute
。如果现在将此对象传递给 GetCommandParamTypes
,您会发现完全相同的问题:您可能希望 return 类型为 [string, void]
,但实际上是 [string, string]
。而且也不矛盾,因为Command<string, string>
有签名
{
execute(input: string): string | string | Observable<string | string>
}
这与 Command<string, void>
的签名没有区别。所以你不能那么容易地输入它。
当您尝试 GetCommandParamType<Command<string, void>>
时它会正常工作的原因,即使 Command<string, void>
的签名如上所述并且您可能希望它也被错误地推断出来,是因为typescript 的编译器实现。我假设当检查类型 T
是否是 Command
的实例时,它首先检查 T
是否是 Command
。在这种情况下,它 是 字面意思和 Command
的实例,因此它只是跳过比较结构并产生正确的类型参数。然而 TypeOfACommandObject
和 TypeACommand
不是 Command
的直接实例,即使后者实现了它,所以这个快捷方式失败了。不过这是一个猜测,我不熟悉打字稿的实现。
说到修复它,如果不重构 execute
的 return 类型,我真的想不出什么好方法,这样 T
和 U
就不会了合并。我可以想到品牌类型,例如使用 unique symbol
类型向 return 类型的命令添加特殊的唯一签名,但是在实现 Command
和在外部使用它时都很难同时使用.您可能应该尝试以某种方式将 T
和 U
分开
更新:
在更新的示例中,由于 GetCommandTypeParams
type ReplaceNever<T, U> = [T] extends [never] ? U : T;
type GetCommandParamTypes<T extends Command<unknown, unknown>> =
T extends Command<infer IO, infer A> ?
[IO, ReplaceNever<Exclude<A, IO>, void>] :
never;
如果你在第一个例子中坚持同样的定义,你会发现它也几乎按预期工作,唯一的问题是最后的 class 和 Observables
。所以你需要稍微调整一下
type GetCommandParamTypes<T extends Command<unknown, unknown>> =
T extends Command<infer IO, infer A> ?
[IO, ReplaceNever<A extends Observable<infer U> ? Exclude<U, IO> : Exclude<A, IO>, void>] :
never;
这个解决方案有一些奇怪的地方,比如你不能完全从实现 Command<string | number, string | Function>
的 classes 中提取类型,但是如果你能接受它就没问题