[英]Is there a way in TypeScript to get typing from a generic that extend a fixed interface?
問題是:
我有一個 class 有一些方法可以進行特定的 api 調用。 這個 class 作為構造函數參數獲取一個接口,其中有一組方法用於將 API 響應轉換為其他內容。 但是從API class來看,是不需要知道transformer實現的,這種方式可以用任何方式實現transformer。 我想做的是在一切都被實例化后知道返回的類型。
所需行為的代碼示例
type SomeObj = {
stringKey: string;
numberKey: number;
}
type SomeOtherObj = {
stringKey: string;
numberKey: number;
}
function fetch1() {
return { stringKey: 'stringKey', numberKey: 2 }
}
function fetch2() {
return { stringKey: 'stringKey', numberKey: 2 }
}
interface IATransformer {
transformResOne: (res1: SomeObj) => any;
transformResTwo: (res2: SomeOtherObj) => any;
}
class ATransformer {
transformResOne(res1: SomeObj): string {
return res1.stringKey
}
transformResTwo(res2: SomeOtherObj): number {
return res2.numberKey;
}
}
interface IA<T extends ATransformer> {
getOne: (param: string) => any; // Not "any" but something from the generic
getTwo: (param2: string) => any; // Not "any" but something from the generic
}
class A<T extends ATransformer> implements IA<T> {
constructor(
private readonly transformer: T
) { }
// Return type doesn't work in this way but don't know how to sobstitute it
getOne(param: string): ReturnType<this.transformer.getOne> {
const res = fetch1()
return this.transformer.transformResOne(res)
}
// Return type doesn't work in this way but don't know how to sobstitute it
getTwo(param2: string): ReturnType<this.transformer.getTwo> {
const res = fetch2();
return this.transformer.transformResTwo(res)
}
}
const transformer = new ATransformer();
// Generic is inferred from the parameter
const a = new A(transformer);
// Desired result ---> ERROR: the type returned from the function is not number but string
const resOneResult: number = a.getOne('Some string')
我已經嘗試過使用 generics 並且我最好的猜測是這樣的:
class A implements IA {
constructor(
private readonly transformer: IATransformer
) { }
getOne(param: string): ReturnType<typeof this.transformer.transformResOne> {
const res = fetch1()
return this.transformer.transformResOne(res)
}
getTwo(param2: string): ReturnType<typeof this.transformer.transformResTwo> {
const res = fetch2();
return this.transformer.transformResTwo(res)
}
}
但是當然不行!
給定一個擴展IATransformer
的泛型類型T
,類型T
的transformResOne()
方法的返回類型可以寫為ReturnType<T['transformResOne']>
,使用ReturnType
實用程序類型獲取返回類型,而T[K]
索引訪問運算符以獲取鍵"transformResOne"
處的T
成員的類型。 並且可以對transformResTwo
做出類似的聲明。 這意味着我們可以這樣定義IA
和A
:
interface IA<T extends IATransformer> {
getOne: (param: string) => ReturnType<T['transformResOne']>;
getTwo: (param2: string) => ReturnType<T['transformResTwo']>;
}
class A<T extends IATransformer> implements IA<T> {
constructor(
private readonly transformer: T
) { }
getOne(param: string): ReturnType<T['transformResOne']> {
const res = fetch1()
return this.transformer.transformResOne(res)
}
getTwo(param2: string): ReturnType<T['transformResTwo']> {
const res = fetch2();
return this.transformer.transformResTwo(res)
}
}
這應該可以按您的意願工作:
const transformer = new ATransformer();
const a = new A(transformer);
const resOneResult: number = a.getOne('Some string'); // error!
// Type 'string' is not assignable to type 'number'
但是,請注意,在A
的實現中,編譯器不能真正遵循這種涉及條件類型(如ReturnType
)的高階類型雜耍,該條件類型依賴於未解析的泛型類型參數(如T
)。
相反,它需要一些捷徑,例如推斷this.transformer.transformResOne(res)
返回與IATransformer
的transformResOne(res)
方法所做的相同的事情,即any
type 。 any
類型是故意松散的,因此如果您錯誤地使用它,編譯器不會注意到或警告您:
getOops(param2: string): ReturnType<T['transformResTwo']> {
const res = fetch2();
return this.transformer.transformResOne(res); // no error, inferred as any
}
所以你必須小心這樣的實現。
如果你想確保更多的類型安全,我建議盡可能避免使用這種條件類型。 一種仍然允許良好類型推斷的方法是重構以使所有返回類型T1
和T2
中的所有內容都通用:
interface IA<T1, T2> {
getOne: (param: string) => T1;
getTwo: (param2: string) => T2;
}
interface IATransformer<T1, T2> {
transformResOne: (res1: SomeObj) => T1;
transformResTwo: (res2: SomeOtherObj) => T2;
}
class A<T1, T2> implements IA<T1, T2> {
constructor(
private readonly transformer: IATransformer<T1, T2>
) { }
getOne(param: string) {
const res = fetch1()
return this.transformer.transformResOne(res)
}
getTwo(param2: string) {
const res = fetch2();
return this.transformer.transformResTwo(res)
}
getOops(param2: string): T2 {
const res = fetch2();
return this.transformer.transformResOne(res); // error!
}
}
正如您所提到的,您可能不想對具有大量類型參數的東西執行此操作,但是您可以看到編譯器在捕獲錯誤和推斷類型方面要好得多(您甚至不必注釋返回類型getOne()
和getTwo()
)。 讓我們確保它仍然有效:
const transformer = new ATransformer();
const a = new A(transformer);
const resOneResult: number = a.getOne('Some string'); // error!
// Type 'string' is not assignable to type 'number'
看起來不錯!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.