すもぎのめも

いろいろあったことをメモしています

「初めてのTypeScript」を読んだ

なぜ読んだか

TypeScript で個人開発したかったのと、業務で TypeScript を使用してきたものの、他の静的型付け言語の雰囲気で TypeScript を使用している感があったため、再入門用に読むことにした。

どんな本か

ある程度 JavaScript を理解しているプログラマー向けに TypeScript の機能を教えてくれる本。深いところまでは言及されておらず、若干実践的ではないので、他の本や実際のプロジェクトと併用して勉強に充てるのがよさそう。訳者の注釈が親切。

読書メモ

型

  • 3.1 合併型 (union type)
    ある値の取り得る型の数を、2つ以上に拡大する。
// 型: string | undefined
let gatha = Math.random() > 0.03
    ? "SSR"
    : undefined;
  • 3.2 型の絞り込み (type narrowing)
    ある値が、定義された型、宣言された型、あるいは以前に推論された型よりも限定的な型であることを TypeScript にコードで示すこと。 変数割り当て、条件チェック、typeof チェックで型を絞り込む。
// 割り当てによる絞り込み
let numberOrString: number | string;
numberOrString = "Gakuen"

// 文字列が割り当てられているので toUpperCase を呼び出し可能
numberOrString.toUpperCase();

// 数値ではないのでエラー
numberOrString.toFixed();
// typeof チェック
let numberOrString = Math.random() > 0.5
    ? 551
    : "Horai"

if (typeof numberOrString === "string") {
    numberOrString.toLowerCase()
} else {
    numberOrString.toFixed()
}
  • 4.2 構造的型付け (structual typing)
    型が定義されていなくても、同じメンバーを含んでいれば、その型として扱える。ダックタイピングとの違いは、静的に型付けされるか、実行時チェックかどうか。

インターフェース

  • 7.2.4 呼び出しシグネチャ
    プロパティが追加された関数を記述できる。関数の宣言後にプロパティを割り当てると module 型に推論される。
interface FunctionWithCount {
  count: number;
  (): void;
}

let hasCallCount: FunctionWithCount;

// この時点ではエラー
// プロパティ 'count' は型 '() => void' に存在しません。
function keepsTrackOfCalls() {
  keepsTrackOfCalls.count += 1;
  console.log(`I've been called ${keepsTrackOfCalls.count} times!`);
}
interface FunctionWithCount {
  count: number;
  (): void;
}

let hasCallCount: FunctionWithCount;

// module keepsTrackOfCalls
function keepsTrackOfCalls() {
  keepsTrackOfCalls.count += 1;
  console.log(`I've been called ${keepsTrackOfCalls.count} times!`);
}

// プロパティを付与するとエラーが無くなる
keepsTrackOfCalls.count = 0;

クラス

  • 8.3 型としてのクラス
    TypeScript の構造的型付けでは、オブジェクトの形状(メンバー、型)をチェックし、オブジェクトがどのように宣言されたかは気にしないので、シグネチャのクラスのインスタンスでなくても渡すことができる。ただ実際のプロジェクトで起こることは少なそう。

  • 8.5 クラスの拡張
    C# だとサブクラスの変数にベースクラスのインスタンスを代入できないが、TypeScript は構造的型付けを採用しているので、サブクラスのメンバーがオプションプロパティのみの場合にベースクラスのインスタンスの代入が可能となる。

型修飾子

  • 9.3.2.1 keyof typeof
    eyof と typeof の組み合わせで型名を書くことなくキーをチェックできる
const parameters = {
    vocal: 52,
    dance: 150,
    visual: 100
};

// keyof と typeof の組み合わせで型名を書くことなくキーをチェックできる
function exam(key: keyof typeof parameters) {
    console.log(parameters[key])
}

exam("vocal");
exam("sence")

ジェネリック

  • 10.8 ジェネリックを適切に使う
    人類、ジェネリックのある言語を学ぶとジェネリックを乱用して謎に複雑なコードを書いてしまいがち。 目的を明確にしてジェネリックを使用するべし。 ジェネリックの黄金律→ The Golden Rule of Generics

構成オプション

  • 13.4.2 resolveJsonModule
    resolveJsonModule が true に設定されている場合、.json ファイルが読み込まれる。オブジェクトをエクスポートしている .ts ファイルのように評価され、他のコードからは const 変数のように使用できる。

名前空間

  • 14.4 名前空間

    DefinitelyTyped で既存のパッケージの型定義を作成する場合を除いて、名前空間は使用しないでください。名前空間は、最新の JavaScript モジュールのセマンティクスと合致しません。

名前空間の代わりに ECMAScript モジュールを用いた最新の標準に合わせる。TypeScript 自体も過去には名前空間を使用していたが、TypeScript 5.0 でモジュールへと移行が完了した。C# の雰囲気で使ってしまうところだった。危ない危ない……

マップ型

  • 15.1.2 修飾子の変更
    他の型やオブジェクトから新しい型を作成できるが、readonly やオプションのアクセス制御修飾子を使用して型を拡張できる。
interface Idle {
    vocal: number;
    dance: number;
    visual: number;
}

type ReadonlyIdle = {
    readonly [K in keyof Idle]: Idle[K];
}

let kuramoto = {
    vocal: 57,
    dance: 90,
    visual: 120
}

const exam: ReadonlyIdle = kuramoto;
console.log(exam)

おわりに

次のステップには訳者補記にも書かれている プログラミングTypeScript ―スケールするJavaScriptアプリケーション開発 が良さそう。

ところで TypeScript の型に関するエラーメッセージが分かりづらいことがある。ChatGPT などにコードとエラーについて聞いてみると解説してくれるので便利。