TypesScriptチップ:リターンオフオーバーロードに条件型を使用する.


導入


数日前、リファクタリングコードを見つけました.
interface IRawUser {
  first_name: string
  email: string,
  id: number,
}

interface User {
  name: string,
  email: string
  print: () => void
}

declare function userFactory(rawUser: IRawUser): User;

function mapRawToUserObject(rawShow: IRawUser[]): User[];
function mapRawToUserObject(rawShow: IRawUser): User;
function mapRawToUserObject(rawShow: IRawUser | IRawUser[]): User | User[] {
  if (rawShow instanceof Array) {
    return rawShow.map((raw) => userFactory(raw));
  }

  return userFactory(rawShow);
}
機能mapRawToUserObject オーバーロードを使用して次のように表現します.IRawUser[] を返します.User[] , 我々がそれをシングルで呼ぶならばIRawUser シングルを返すUser .
あまり複雑ではありませんが、メソッド定義を再構成する良い機会のように見えました.

条件型を作成する


メソッドが受け取る入力の種類を決定する必要があります.
調べる必要のある入力は以下の通りです.IRawUser and IRawUser[]
type MapRawResult<T extends IRawUser | IRawUser[]> = any;
次に、条件付き論理を追加します.
type MapRawResult<T extends IRawUser | IRawUser[]> = T extends IRawUser ? User : User[]

In plain words: If T is assignable to IRawUser return User otherwise return User[]



条件式をこのメソッドに適用します。


現在の方法は次のようになります.
function mapRawToUserObject(rawShow: IRawUser[]): User[];
function mapRawToUserObject(rawShow: IRawUser): User;
function mapRawToUserObject(rawShow: IRawUser | IRawUser[]): User | User[] {
  // implementation
}
これでオーバーロードを削除し、ジェネリックパラメーターを追加できますT extends IRawUser | IRawUser[] ), を返します.
type MapRawResult<T extends IRawUser | IRawUser[]> = T extends IRawUser ? User : User[]

function mapRawToUserObject<T extends IRawUser | IRawUser[]>(rawShow: T): MapRawResult<T> {
  // implementation
}
この時点で、typescriptは私たちが返す価値のタイプについて不満を言うでしょう.

コンパイラは、返される値の型を推論できません.この解決方法は、キーワードを使用して期待している型を明示的に表現することですas .
最終的な方法は次のようになります.
function mapRawToUserObject<T extends IRawUser | IRawUser[]>(rawShow: T): MapRawResult<T> {
  if (rawShow instanceof Array) {
    return rawShow.map((raw) => userFactory(raw)) as MapRawResult<T>
  }

  return userFactory(rawShow) as MapRawResult<T>
}

結論


いくつかの値を試してみると、このメソッドは以前と同じように動作しますが、オーバーロードを使用することはできません.


ボーナスとして、私たちはIRawUser | IRawUser[] コードクリーナーを作成します.
type MapRawArg = IRawUser | IRawUser[];

type MapRawResult<T extends MapRawArg> = T extends IRawUser ? User : User[];
function mapRawToUserObject<T extends MapRawArg>(rawShow: T): MapRawResult<T> {
  // ...
}

更なる読書

  • Conditional Types
  • Type Assertions
  • Generics
  • TypeScript Playground