問題
ユニオン型のジェネリクスを使用した値を、配列のメソッドに適用したときに発生した問題です。
以下が、エラーが発生するサンプルコードです。
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つの型として処理されます。