簡體   English   中英

TypeScript 中有沒有辦法從擴展固定接口的泛型中獲取類型?

[英]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 ,類型TtransformResOne()方法的返回類型可以寫為ReturnType<T['transformResOne']> ,使用ReturnType實用程序類型獲取返回類型,而T[K] 索引訪問運算符以獲取鍵"transformResOne"處的T成員的類型。 並且可以對transformResTwo做出類似的聲明。 這意味着我們可以這樣定義IAA

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)返回與IATransformertransformResOne(res)方法所做的相同的事情,即any type any類型是故意松散的,因此如果您錯誤地使用它,編譯器不會注意到或警告您:

getOops(param2: string): ReturnType<T['transformResTwo']> {
  const res = fetch2();
  return this.transformer.transformResOne(res); // no error, inferred as any
}

所以你必須小心這樣的實現。


如果你想確保更多的類型安全,我建議盡可能避免使用這種條件類型。 一種仍然允許良好類型推斷的方法是重構以使所有返回類型T1T2中的所有內容都通用:

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'

看起來不錯!

Playground 代碼鏈接

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM