ウェブサイト検索

TypeScript - ジェネリックキーでオブジェクトにインデックスを付ける


この問題に初めて直面したので、タイトルが意味をなさない場合はすみません、うまく言い表す方法が見つかりませんでした。

私はこのようなレスポンスを返すAPIを使っています:

{
  PaginatedResults: {
    TotalResults:  100,
    Offset: 0,
    Limit: 0,
    PageSize: 100,
  },
  Error: {
    Message: '',
    Code: '',
  },
  Customers: [{...}]
}

Customers は、API が返す任意の型に置き換えることができます。したがって、次のように見えることもあります:

{
  PaginatedResults: {
    TotalResults:  100,
    Offset: 0,
    Limit: 0,
    PageSize: 100,
  },
  Error: {
    Message: '',
    Code: '',
  },
  Sales: [{...}]
}

私は現在、このように型を表現しています:

type PaginatedResponse<TResponse extends object> = 
  TResponse & 
   Readonly<{
    PaginatedResponse: {
      TotalResults:  number;
      Offset: number;
      Limit: number;
      PageSize: number;
    };
    Error?: {
      Message: string;
      Code: string;
    };
  }>;

これは順調に動作していましたが、最近、あるエンドポイントを自動でページネーションする汎用のヘルパー関数を作ろうとしたときに問題に直面しました。

すべての結果を追跡するために、私はこのようなことをする必要があります:

const allResults = [...initialResults];
const nextResp = await apiCall();
allResults.push(...nextResp['Customers'])

しかし問題は、キー 'Customers' が API が返す可能性のある 50 種類以上のタイプのどれであってもよいということです。ですので、レスポンスに汎用的にインデックスを付ける方法が必要です。いくつかの方法を試しましたがうまくいかなかったので、このようなことにどのように取り組むのが最良かについての指針が欲しいと思っています。

回答 #1

TypeScriptだけではここではあまり役に立ちません。...nextResponse['Customers'] では、TypeScriptが正しいキーを補完することを期待しています。しかし、正しいキーが何であるかは、実行時に知られるか、または手動で指定したジェネリック型によってのみ知られることになります。

でも、これはとにかくあなたの助けになるかもしれません:

// I added some generic types here which will help later
type PaginatedResponse<TResponse extends {[key in TKey]: any}, TKey extends keyof TResponse = keyof TResponse> = 
   Readonly<{
    PaginatedResponse: {
      TotalResults:  number,
      Offset: number,
      Limit: number,
      PageSize: number,
    };
    Error?: {
      Message: string;
      Code: string;
    };
  }> & TResponse

可能なResponseタイプを定義してください:

type Response1 = PaginatedResponse<{Customers: any}>
type Response2 = PaginatedResponse<{Sales: any}>
type AllResponses = Response1 | Response2

では、PaginatedResponse を取得し、TResponse を配列に追加する関数を定義しましょう:

type GetResponseKey<T> =  T extends PaginatedResponse<infer TRes, infer TKey> ? TKey : never

const arr: any[] = []

function pushToResponseArray<
  T extends AllResponses, 
  K extends GetResponseKey<T>
>(response: T, key: K){
  arr.push(response[key])
}

const res: Response1 = {} as any

pushToResponseArray(res, "Customers") // We have to manually specify the key here
// but typescript will check if key is correct

これは完璧な解決策ではなく、常にkeyを手動で設定するか、何らかのJavaScriptのロジックで決定する必要があると思います。

まとめると:

あなたのアプリケーションのどこかで、PaginatedResponse のタイプを判定するための JavaScript ロジックが必要です。その後、タイプを対応するキーにマッピングして TResponse にアクセスします。

サンドボックス