理系公務員のプログラミング日記

【りあクト!1】TypeScript 高度な型表現

タグ:
JavaScript
TypeScript

TypeScriptはなんとなく書いてて全然分かってなかったけど、この本のおかげで理解がかなり進んだ気がする。

型引数

型引数(Type Parameter)は関数に渡す引数と同じで、任意の型を<>によって引数として渡すことで、 その関数の引数や戻り値の型に対応できるようになる。

const toArray = <T>(arg1: T, arg2: T): T[] => [arg1, arg2]; >toAttay(8,3); [8,3] >toArray('foo','bar'); ['foo','bar'] >toArray(5,'bar') // コンパイルエラー

この例では、1つ目と2つ目は型推論によってnumber型、string型が当てられているが、型引数によって arg1とarg2は同じ型を持つことが指定されているので、3つ目は型推論では型を一致できずエラーになる。

このように、データの型に束縛されないよう型を抽象化してコードの再利用性を向上させつつ、静的型付け言語の持つ型安全性を維持するプログラミングを ジェネリックプログラミングと呼ぶ。そして、型引数を用いて表現するデータ構造のことをジェネリクスと呼ぶ。

可変長引数を使うと、個数を指定せずにジェネリクスを作成できる。

const toArrayVariably = <T>(...args: T[]): T[] => [...args];

条件付き型

extends キーワードと三項演算子を併用することで任意の条件による型の割り振りができる。 関数の型から任意の引数の型を抽出したりもできる。

type User = { id: unknown }; type NewUser = User & { id: string }; type OldUser = User & { id: number }; type Book = { isbn: string }; // 条件付き型 型TがUserを拡張していれば True そうでなければFalse type IdOf<T> = T extends User ? T['id']: never; type NewUserId = IdOf<NewUser>; // string; type OldUserId = IdOf<OldUser>; // number; type BookId = IdOf<Book>; // never;

またinferキーワードによって、マッチング中に取得した型を出力に利用することもできる。

type Flatten<T> = T extends Array<infer U> ? U : T; const num = 5; const arr = [3,6,9]; type A = Flatten<typeof arr>; // number type N = Flatten<typeof num>; // number

型Tが何らかの型の配列(Arrayの拡張)だった場合、その配列の中身の型を infer U で型 U として取得できる。 配列出なかった場合、そのまま型が出力される。

テンプレートリテラル型

テンプレートリテラル型を使うことで、これまで文字列でベタ書きしていたクエリやパスにも型をつけることができる。

const tables = ['users','posts','comments'] as const; type Table = typeof tables[number]; type AllSelect = `SELECT * FROM ${Table}`; type LimitSelect = `${AllSlect} LIMIT ${number}`; const createQuery = (table:Table , limit?:Limit): AllSelect | LimitSelect => limit ? `SELECT * FROM ${table} LIMIT ${limit}` as const : `SELECT * FROM ${table}` as const; const query = createQuery('users',20); console.log(query)

テンプレートリテラル型と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>; // 'users'|'posts'|'comments';

Tがsql文の拡張であった場合、そのテーブル名を型として取得できる。かな?

自由過ぎて型の概念が分からなくなりそう。