6時だョ!!全員集合!!

Rails・JavaScrictを中心にアウトプットします。

2022年4月4日 りあクト! 第4章 TypeScriptで型をご安全に 4-5 条件付き型とテンプレートリテラル型(p.191~)

テンプレートリテラル

TypeScriptでは、テンプレートリテラルを型定義として扱うことが出来ます。

type DateFormat = `${number}-${number}-${number}`;

const date1: DateFormat = '2020-12-05';
const date2: DateFormat = 'Dec.5,2020'; // compile error!
  • エイリアスの定義時にテンプレートリテラルを使う事で型の定義ができます。
  • number型をハイフン区切りで3つ羅列する型定義をしているため、それ以外ではコンパイルエラーになります。

以下は少し応用した例です。

const tables = ['users', 'posts', 'comments'] as const;
type Table = typeof tables[number];
type AllSelect = `SELECT * FROM ${Table}`;
type LimitSelect = `${AllSelect} LIMIT ${number}`;

const createQuery = (table: Table, limit?: number): AllSelect | LimitSelect =>
  limit ? `SELECT * FROM ${table} LIMIT ${limit}` as const
  : `SELECT * FROM ${table}` as const;
const query = createQuery('users', 20);
console.log(query);
  • as constで配列の要素の型をリテラル型として認識できます。
  • tables[number]で配列の要素を全て表し、typeofで型を共用体型で抽出したものにTableという型の参照名を付けます。(型エイリアス
  • AllSelectはテンプレートリテラルと共用体型を組み合わせた型です。
  • createQueryにはTable型と、任意で数値を渡すことができます。
  • 数値を第二引数で渡している場合は三項演算子がtrueとなりLimitSelect型 の値を返し、省略している場合はAllSelect型(LIMITなし)の値を返します。

出力結果: "SELECT * FROM users LIMIT 20"

以下の例では、テンプレートリテラル型の中でinferを使用してます。

const q1 = 'SELECT * FROM users';
const q2 = 'SELECT id, body, createdAt FROM posts';
const q3 = 'SELECT userId, postId FROM comments';

type PickTable<T extends string> = T extends `SELECT ${string} FROM ${infer U}` ? U : never;

type Tables = PickTable<typeof q1 | typeof q2 | typeof q3>;
  • クエリ文字列から各クエリのテーブル名を抜き出すことができます。

共用体型と条件型を組み合わせると以下のような演算が行われるので、注意が必要です。条件型に対して分配法則が適用されています。

T1 | T2 extends U ? A : B
// ↓
(T1 extends U ? A : B) | (T2 extends U ? A : B)

組み込みユーティリティ型

Partial<T>, Required<T>, Readonly<T>

Partial<T>, Required<T>, Readonly<T>をオブジェクト型に使うことで、一括でプロパティを省略可能にしたり、読み取り専用にすることができます。

type Yano = {
  skill: 'JS';
  name: 'kohei';
}

// Yanoのプロパティを全て省略可能にする
type PartialYano = Partial<Yano>;
// => type PartialYano = {
//      skill?: "JS" | undefined;
//      name?: "kohei" | undefined;
//    }

// Yanoのプロパティを全て必須にする
type RequiredYano = Required<Yano>;
// => type RequiredYano = {
//      skill: 'JS';
//      name: 'kohei';
//    }

// Yanoのプロパティを全て読み取り専用にする
type ReadonlyYano = Readonly<Yano>;
// => type ReadonlyYano = {
//      readonly skill: 'JS';
//      readonly name: 'kohei';
//    }

Pick<T, K>, Omit<T, K>

Pick<T, K>, Omit<T, K>をオブジェクト型に使うことで、オブジェクト型からプロパティを抽出したり、省くことができます。

type Yano = {
  skill: 'JS';
  name: 'kohei';
  ken: 'hyougo'
}

// Yanoのskillとnameのみを抽出した型を返す
type PickedYano = Pick<Yano, 'skill' | 'name'>;
// => type PickedYano = {
//      skill: 'JS';
//      name: 'kohei';
//    }

// Yanoのkenプロパティを省いた型を返す
type OmittedYano = Omit<Yano, 'ken'>;
// => type OmittedYano = {
//      skill: 'JS';
//      name: 'kohei';
//    }

Extract<T, U>, Exclude<T, U>

ExtractM<T, U>は、TからUの要素を抽出した型を返します。Exclude<T, U>は、TからUの要素を省いた型を返します。列挙的な型に対して使います。

type Permission = 'r' | 'w' | 'x';

type RW1 = Extract<Permission, 'r' | 'w'>; // "r" | "w"
type RW2 = Exclude<Permission, 'x'>; // "r" | "w"

参考

りあクト! 【Ⅰ. 言語・環境編】 p.191~194