[PR] 本記事にはプロモーションが含まれています。

【TypeScript】記号の多いコードの読み方|アロー関数の型、Partial・Pick などのユーティリティ型、?.・??・! の意味

TypeScript

TypeScriptのコードを読んでいると、(x: number): number => や Pick<User, "id">、?. や ! のような記号が続けて出てきて、どこで区切って読めばいいのか迷うことがあります。C言語の関数ポインタの宣言を初めて読んだときに似た、「記号の意味が分かれば読めるのに」というもどかしさです。

この記事では、TypeScriptのコードによく出てくる記号を、「アロー関数の型」「ユーティリティ型」「null を扱う記号」に分けて読み解きます。アロー関数そのものの書き方は「アロー関数(=>)とは?」で紹介しているので、ここでは型の部分を中心に見ていきます。コードはTypeScript Playgroundに貼れば試せます。

記号の早見表

先に、この記事で扱う記号をまとめておきます。

記号意味例
名前: 型その変数や引数の型(x: number)
?:省略できるプロパティage?: number
型の中の =>関数の戻り値の型(x: number) => number
<T>型を後から決める(ジェネリクス)<T>(items: T[]) => ...
?.null か undefined なら、そこで止めて undefineduser.address?.city
??null か undefined なら、右の値を使うcity ?? "未登録"
後ろに付く !null ではないと TypeScript に伝えるinput!.value
as型を上書きするel as HTMLInputElement

アロー関数の型の読み方

引数の型と戻り値の型

TypeScript
const double = (x: number): number => x * 2;

この1行は、次の3つに分けて読めます。

部分意味
(x: number)引数 x は number 型
) の後ろの : number戻り値は number 型
=> x * 2中身(x * 2 を返す)

) と => の間にある : number が戻り値の型です。C言語の int double_it(int x) は戻り値の型を先に書きますが、TypeScriptでは引数の後ろに書きます。

型の中に出てくる => は「戻り値の型」

=> は、関数の「型」を書くときにも使われます。こちらは中身ではなく、戻り値の型を表します。

TypeScript
// 「number を受け取って number を返す関数」という型
type NumberFn = (x: number) => number;

// 型が決まっているので、x の型は書かなくてよい
const triple: NumberFn = x => x * 3;

type や変数の : の後ろなど、型を書く場所にある => は「戻り値の型」、= の右側の値の場所にある => は「アロー関数の中身」、と分けて読みます。C言語の typedef int (*NumberFn)(int); に当たるものです。

型を省けるのは、使われる場所から型が分かるときだけ

上の triple のように、引数の型を書かなくてよいのは、まわりから型が分かるときです。配列の map に渡す関数も、配列の中身から型が分かります。

TypeScript
const prices = [100, 250, 80];
const withTax = prices.map(p => p * 1.1); // p は number と分かる

一方、何も手がかりがないところで引数の型を省くと、エラーになります。

TypeScript
const calc = x => x * 2; // x が何の型か分からない
Plaintext
Parameter 'x' implicitly has an 'any' type.

「x の型が分からないので、何でも入る any 型になっている」という意味です。strict の設定(新しいTypeScriptでは初期設定でオン)では、これがエラーになります。短いコードを読むときは、「この引数の型はどこから来ているのか」を探すと、意味が分かりやすくなります。

<T> は「あとから決まる型」

関数の前に <T> が付いているのは、ジェネリクスです。T は「呼び出すときに決まる型」の付け替え用の名前で、数値の配列を渡せば number、文字列の配列を渡せば string になります。

TypeScript
// 配列の最初の要素を返す(配列が空なら undefined)
const firstOf = <T>(items: T[]): T | undefined => items[0];

const n = firstOf([10, 20]);   // n は number | undefined
const s = firstOf(["a", "b"]); // s は string | undefined

T | undefined の | は「または」という意味です。Reactの .tsx ファイルでは、<T> がタグと紛らわしいので、<T,> と書かれていることもあります。

ユーティリティ型:元の型を加工する

Partial<User> のように <> で型を渡すものは、TypeScriptに最初からある「型を加工する型」(ユーティリティ型)です。「元の型」と「何をしたか」に分けて読むと分かりやすくなります。

TypeScript
type User = {
  id: number;
  userName: string;
  email: string;
  age?: number; // 省略できる
};

type UserDraft   = Partial<User>;                 // すべて省略できるようにする(入力の途中など)
type FullUser    = Required<User>;                // すべて必須にする(age も必須)
type UserPreview = Pick<User, "id" | "userName">; // id と userName だけ取り出す
type PublicUser  = Omit<User, "email">;           // email だけ取り除く
type FixedUser   = Readonly<User>;                // すべて書き換えできなくする
type ScoreMap    = Record<string, number>;        // キーが文字列、値が数値のオブジェクト

Pick や Omit の2つ目には、プロパティ名を文字列で渡します。複数のときは "id" | "userName" のように | でつなぎます。エディタで UserPreview にマウスを当てると、加工したあとの型が表示されるので、読むときはそれを見るのがいちばん確実です。

null を扱う記号:?.、??、!

?. と ??:ないかもしれない値を安全に読む

TypeScript
type Profile = {
  address?: { city?: string };
};

const profile: Profile = {};

// address がなければ、?. のところで止まって undefined になり、?? で "未登録" になる
const city = profile.address?.city ?? "未登録";
console.log(city); // 未登録

?? と || の違いは「??(Null合体演算子)と三項演算子の違い」で紹介しています。

後ろの !:null ではないと「約束」する

変数の後ろに付く ! は、「この値は null や undefined ではない」と TypeScript に伝える記号です。ただし、実行するときには何も確かめません。本当に null だった場合は、そのままエラーになります。

TypeScript
// ! を使う書き方:input 要素がないと、実行時にエラーになる
const input1 = document.querySelector("input")!;
console.log(input1.value);

// 確かめてから使う書き方
const input2 = document.querySelector("input");
if (input2) {
  console.log(input2.value);
}

C言語でいえば、NULLチェックをしないままポインタを使うのに近い書き方です。読むときに ! を見つけたら、「ここは書いた人が null ではないと判断した場所」だと考えると、不具合を探すときの手がかりになります。document.querySelector については「querySelectorで使えるCSSセレクターの書き方」で紹介しています。

as と satisfies:型についての指示

TypeScript
// as:型を上書きする(実行時に値を変換するわけではない)
const priceInput = document.querySelector("#price") as HTMLInputElement;

// satisfies:型に合っているかを確かめる(推論された型はそのまま)
type Config = { port: number; host: string };
const config = { port: 3000, host: "localhost" } satisfies Config;

as も ! と同じように、TypeScriptに「この型だと思ってくれ」と伝えるだけです。#price の要素がなければ priceInput は null になりますが、TypeScriptはそれに気づきません。satisfies は、型に合っていなければエラーにしつつ、値から推論した型をそのまま使える書き方です。

短いコードを読むときのコツ

  • => を見たら、型の場所(戻り値の型)か、値の場所(関数の中身)かを分ける
  • 引数に型が書いていなければ、その型がどこから来ているか(map の配列、変数の型など)を探す
  • Partial<〜> などは、元の型と加工の内容に分けて読み、エディタで加工後の型を確かめる
  • ! と as は「書いた人の約束」。実行時には確かめられていない

分からない記号が出てきたら、Playgroundに貼ってマウスを当て、型を表示させてみるのが近道です。Playgroundの使い方は「公式サイト typescriptlang.org の使い方」、[a, b] のような配列の型は「タプル(Tuple)とは?」で紹介しています。

まとめ

  • アロー関数では、(x: number) が引数の型、) の後ろの : number が戻り値の型
  • 型の中の => は戻り値の型。(x: number) => number は「number を受け取って number を返す関数」
  • 引数の型を省けるのは、使われる場所から型が分かるときだけ
  • ユーティリティ型は、元の型を加工する型(Partial、Required、Pick、Omit、Readonly、Record)
ほかの開発・プログラミングの記事は、開発・プログラミングメモの目次から、テーマ別に探せます。

このブログは、にほんブログ村と人気ブログランキングに参加しています。記事が役に立ったら、下のランキングのバナーのどれかを押して応援してもらえると励みになります。

ブログランキング・にほんブログ村へにほんブログ村

サーバー構築・運用ランキング プログラムランキング

TypeScript
watasoftdevをフォローする

コメント