背景
我有一個介面,它宣告了一個方法,其回傳型別取決于提供給介面的型別引數。
interface Command<T, U = void> {
execute(input: T): [U] extends [void] ? T | Observable<T>: T | U | Observable<T|U>;
}
然后我有一個實用程式型別,它應該回傳型別引數T和U提供的型別。
type GetCommandParamTypes<T extends Command<unknown, unknown>> =
T extends Command<infer IO, infer A> ?
[IO, A] :
never;
問題
操場
盡管GetCommandParamTypes在提供Command介面時按預期作業,但我正在努力除錯為什么我在為它提供實作該介面的類時沒有得到我預期的結果。
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 以推斷型別引數時會發生什么或將我指向可能有助于解釋它的任何資源的方向嗎?
Update 1
Playground
If i remove the conditional type from the command return type (which is not the desired behaviour as the second type parameter is optional and i don't want void in the return type if it's not supplied) it does seem to get closer to what i was expecting. Or at least something a bit more workable.
interface Command<T, U = void> {
execute(input: T): T | U | Observable<T | U>;
}
At first glance it appears that the execute method input type is being returned for infer IO and the return type is being returned for infer A.
But for TypeACommand that has the following signature:
execute(input: string): string | Observable<string>
[string, string] is returned, not [string, string | Observable<string>]
Where as TypeCCommand which has the following signature:
execute(input: string): Observable<string>
[string, string | Observable<string>] is actually returned instead of [string, Observable<string>]
And MixedTypeObservableCommand which also only has an Observable as the return type also returns all the type parameters of the Observable<...> and the Observable itself.
So:
execute(input: string | number): Observable<string | number | Function | boolean>
becomes:
[string | number, string | number | boolean | Function | Observable<string | number | boolean | Function>]
Update 2
This is a link to a previous working example i created, before also adding Observable to the return type of the Command interface.
Playground
There at least appeared to be consistency in the types returned for infer IO and infer A for this version, where IO was the execute methods input type and A was the execute methods return type.
I was then able to use Exclude to calculate the corresponding types in relation to the Command interface
uj5u.com熱心網友回復:
之所以會發生這種情況,是因為在檢查T時GetCommandParamTypes它的一個實體Command實際上并未檢查您提供的類是否實作了該介面。打字稿具有結構,而不是名義分型,因此它的作用是比較的簽名Command<..., ...>和T。型別TypeACommand有簽名
type TypeOfACommandObject = {
execute(input: string): string | Observable<string>
}
沒有什么暗示它實作了Command,它只是一個execute具有特定引數和回傳型別的方法的物件。如果您現在將這個物件傳遞給GetCommandParamTypes您,您會發現完全相同的問題:您可能希望回傳型別是[string, void],但實際上是[string, string]. 并且沒有矛盾,因為Command<string, string>有簽名
{
execute(input: string): string | string | Observable<string | string>
}
這與 的簽名無法區分Command<string, void>。所以你不能那么容易地輸入它。
當您嘗試 時它會正常作業的原因是因為 typescript 的編譯器實作GetCommandParamType<Command<string, void>>,即使 的簽名Command<string, void>如上所述并且您可能希望它也被錯誤地推斷出來。我假設在檢查 typeT是否是Command它的一個實體時,首先檢查 if T is Command。在這種情況下,它是從字面上和實體Command,所以它只是跳過比較結構,并產生正確的型別引數。TypeOfACommandObject而TypeACommand不過是不是直接實體Command,即使后者實作它,所以這個快捷方式失敗。不過,這是一個猜測,我不熟悉打字稿實作。
說到修復它,我真的想不出一個好方法而不重組的回傳型別,execute以便T和U不合并。我可以想到品牌型別,比如添加一個特殊的獨特簽名來使用unique symboltype回傳命令型別,但是在實作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;
如果你在第一個例子中堅持這個相同的定義,你會發現它也幾乎按預期作業,唯一的問題是最后一個帶有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;
沙盒
This solution has a couple of weirdnesses like you won't be quite able to extract types from classes that implement Command<string | number, string | Function> for example, but if you can live with it then it's fine
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/356476.html
