【TypeScript】ユニオン型のジェネリクス使用時、Arrayメソッド(map,filter,sort…)の返り値の型が合わない

問題

ユニオン型のジェネリクスを使用した値を、配列のメソッドに適用したときに発生した問題です。
以下が、エラーが発生するサンプルコードです。

type Dog = {
  type: 'dog'
  id: string
  name: string
  kind: 'poodle' | 'shiba'
}

type Cat = {
  type: 'cat'
  id: string
  name: string
  kind: 'persian' | 'scottish-fold'
}

const sortPetsById = <T extends Dog[] | Cat[]>(list: T): T => {
  const result = list.sort((a, b) => Number(a.id) - Number(b.id))
  return result // この値の型が、指定のT型に割り当てられない、というエラーが出る
}

引数の型がそのまま返り値にも適用されてほしいのですが、エラーが出てしまいます。

型 'Dog[] | Cat[]' を型 'T' に割り当てることはできません。
  'Dog[] | Cat[]' は型 'T' の制約に代入できますが、'T' は制約 'Dog[] | Cat[]' の別のサブタイプでインスタンス化できることがあります。
    型 'Dog[]' を型 'T' に割り当てることはできません。
      'Dog[]' は型 'T' の制約に代入できますが、'T' は制約 'Dog[] | Cat[]' の別のサブタイプでインスタンス化できることがあります。

解決策

ジェネリクスの型Tを、配列の型ではなく、配列の要素の型にします。

const sortPetsById = <T extends Dog | Cat>(list: T[]): T[] => {
  const result = list.sort((a, b) => Number(a.id) - Number(b.id))
  return result
}

参考までに、Array.sortやspliceの型を見てみると、以下のようになっています。

interface Array<T> {
  // ...
  sort(compareFn?: (a: T, b: T) => number): this;
  // ...
  splice(start: number, deleteCount?: number): T[];
  // ...
}

これを見ると、T extends Dog[] | Cat[]だと、処理対象の配列は、Array<Dog> | Array<Cat>と認識されるので、1つの配列型として処理されないことがイメージできるかと思います。

逆に、T extends Dog | Catだと、ユニオン型はあくまで要素の型なので、配列自体は1つの型として処理されます。

タイトルとURLをコピーしました