TypeScript の歩き方 - TypeScript の全機能を概観する
この記事は何?
TypeScript を学ぼうとする人にとって最初のハードルは「どこから手を付ければよいかわからない」ことでしょう。
TypeScript は JavaScript の上に成り立っており、JavaScript もまた、複雑な歴史的経緯の上に成り立っています。JavaScript の深淵な歴史は、TypeScript 学習者に重くのしかかります。
また TypeScript/JavaScript に関連する技術は非常に多岐にわたり、広く普及しています。例えば、DOM, React, Node.js, npm, webpack など...。これらはあまりに普及しているために学習コンテンツにまで浸透してしまっており、純粋な TypeScript の学習にとって雑音になっています。
この記事の前半では「TypeScript とは何であって、何ではないのか」という境界線を明確にします。後半では「TypeScript を学ぶ黄金の順序」を提案します。個別の技術的詳細には触れず、外部のハンドブックサイトや記事に委ねます。
「境界」を明確にして「順序」を提案するだけの記事ですが、まず全容を把握したい初学者にとっては大きな価値になると思います。
熟練者にとっても、かなり細かい部分まで網羅しているので知識の整理に役立つと思います。ご指摘やご提案があればぜひコメントをお願いします。
前半: TypeScript とは何か
TypeScript とそれを構成する機能、それを取り巻く機能を列挙し、TypeScript の本質を理解します。各章のタイトルには、初学者がとるべきスタンスを記載してあります。
JavaScript の機能 -- 概観を把握し、言語の性質を知っておく
TypeScript は JavaScript のスーパーセットとして定義されています。つまり、JavaScript で使える機能はすべて使えます。
「JavaScript」という語は、様々な範囲で用いられることがあります。「TypeScript とは何か」を知るには、「JavaScript とは何か」を知る必要があります。まずはこれを明確にしておきましょう。
以下に、JavaScript を構成する要素を思いつく範囲で分解しました。 純粋に TypeScript という言語を学ぶ上で知っておきたいのは 1. の範囲だけです。 各用語を詳しく理解する必要はありませんが、概観は理解しておくとよいです。
- 純粋にプログラミング言語として
-
ECMAScript
- 文法
- 組み込み API
-
ECMAScript
- ブラウザスクリプトとして (DOM, Fetch API, Web Assembly など)
- サーバーサイド・ネイティブアプリ開発言語として
- 配布モジュールとして
- 拡張フォーマットとして (TypeScript, JSX, Vue, Svelte など)
- トランスパイラー、バンドラー、ポリフィルなどと組み合わせたフレームワークとして (vite, webpack, babel など)
- 専用 API のスクリプトとして (Google Apps Script, Office Scripts など)
JavaScript の学習には MDN Web Docs をお勧めします。Mozilla (Firefox ブラウザなどの開発団体) が運営する、 Web 開発技術に関するドキュメンテーションサイトです。JavaScript の 1. と 2. の側面を網羅しています。
あくまでブラウザ向け JavaScript のドキュメンテーションですので、TypeScript に関する説明はほとんど出てきません。裏を返せば、このドキュメンテーションに登場するものは TypeScript そのものではない、ということです。
JavaScript の機能を列挙するとプログラミングの基礎のコンセプトから話すことになりますので、この記事では詳細に触れることはしません。
型 -- TypeScript の最も重要な機能
TypeScript の主題となる機能です。JavaScript の変数、関数、クラスなどに対して型アノテーションを付与し、コーディング中やトランスパイル時に型の互換性をチェックすることができます。
これらの型情報は実行時にすべて除去されます。つまり、TypeScript で型を定義しても JavaScript の挙動には一切影響を与えません(特別なフレームワークを使わない限り)。
静的型付けの恩恵についてはここでは詳しく説明しません。
この機能こそが TypeScript の最も重要な機能であり、その他は 「TypeScript を学ぶ」という点において単なる雑音 です。
後半の TypeScript を学ぶ順序では、この型の機能にだけフォーカスして学習順を提案しています。
実行時機能 -- ひとまず使わない
TypeScript には、実行可能な JavaScript コードに変換されるいくつかの独自機能があります。
- 名前空間 (
namespace foo {}) - 列挙型 (
enum Foo {}) - クラスのパラメータプロパティ (
constructor (public foo: string) {}) - デコレーター (
@Decorate())
これらは型チェックだけでなく、実行時の挙動にも影響を与えます。
便利さはありますが、どちらかというとややこしさの方が際立つので、初学者は触れないほうが賢明です。
TypeScript Language Server -- 最大限活用する
TypeScript 公式の Language Server です。VS Code などのエディタでコーディング中に、型情報をもとにコード補完やエラー検知を行ってくれます。型と合わせて、最も重要な機能の一つです。
VS Code であれば、拡張機能不要でデフォルトで備わっています。なにせ VS Code 自身が TypeScript 製 (Electron + Monaco Editor) ですからね。
tsc の型チェック -- 活用する
TypeScript Language Server はすべてのファイルを一度にチェックする用途には向いていません。この用途では tsc を使用します。
tsc は TypeScript 公式のコンパイラー(トランスパイラー)です。
トランスパイラーなので TypeScript コードを JavaScript に変換することができますが、私としては tsc の本質的な価値はトランスパイルではない と思っています。tsc より優れたパフォーマンスを発揮するトランスパイラーは多くあります。
tsc の最大の価値は、最も正確な型エラー検知を行う型チェッカーであることです。TypeScript の型システムは非常に複雑であり、実質的に公式の tsc が唯一の選択肢です。tsc --noEmit は開発には必須のコマンドです。
設定ファイル tsconfig.json -- 逃れられない罠
LSP/tsc による型チェック対象ファイルや厳格さの指定、tsc のトランスパイルの設定などを記載します。
かなり難しく、かといって設定しないと使えないので無視するわけにもいかず、非常に躓きやすいポイントです。
型宣言ファイル .d.ts -- ひとまず使わない
型だけを宣言するためのファイルです。JavaScript ソースコードに対応する型を付けるときや、ライブラリを配布するときなどに使います。
自分で書いたコードを動かすだけならあまり考える必要はありませんが、使っているライブラリが提供する型が間違っていたりすると引っかかる可能性はあります。
後半: TypeScript を学ぶ順序
TypeScript の型について学ぶべき順序を紹介します(「TypeScript の型」以外には触れません)。思いつく限り TypeScript の型の機能を列挙してあります。上から順に学ぶとスムーズだと思います。
気になるものを以下の参考サイトや他の記事などで調べてください。
- サバイバル TypeScript: 日本語のハンドブックです。TypeScript と JavaScript の機能を区別せずに紹介しています。
- TypeScript Handbook: 英語のハンドブックです。TypeScript の機能を紹介しています。
- TypeScript Cheat Sheets: 英語のチートシートです。TypeScript の機能を紹介しています。
- TypeScript Playground: 環境構築不要で TypeScript を試せます。
手元に開発環境を構築したい方はこちらの記事を参考にしてください。
基本の型
- 型アノテーション (
let a: string = "foo") - 型推論 (
let a = "foo") - プリミティブ型 (
number,string,boolean,null,undefined,bigint,symbol) - 配列の型 (
string[]) - オブジェクトの型 (
{ foo: string; bar: number }) - 避けるべきラッパーオブジェクト型 (
Object,Number,String,Boolean,Bigint,Symbol) - ユニオン型 (
string | number) - インターセクション型 (
{ foo: number } & { bar: string }) - 型エイリアス (
type Foo = string | number) - インターフェース (
interface Bar { baz: string }) - 読み取り専用プロパティ (
{ readonly foo: string }) - オプショナルプロパティ (
{ foo?: string }) - リテラル型 (
type Foo = "foo")
関数/クラスの型
-
function宣言の型 (function foo(arg: string): string {}) - 関数式リテラルの型 (
const foo = function (arg: string): string {}) - アロー関数リテラルの型 (
const foo = (arg: string): string => {}) - メソッドの型 (
foo(arg: string): string {}) - 関数の型 (
(arg: string) => number) - オプショナルパラメータ (
(arg?: string) => number) - 残余パラメータ (
(...args: string[]) => number) - オーバーロード (
function foo(v: string): string; function foo (v: number): number;) - コールシグネチャ (
{ (v: number): number; (v: string): string }) - クラス宣言の型 (
class Foo implements FooType {}) - クラスメンバー修飾子 (
public,private,protected,override) - 抽象クラス (
abstract class Foo {}) - コンストラクトシグネチャ (
{ new (v: string): Foo })
発展的な型
- タプル (
[string, string, number]) - 読み取り専用配列/タプル (
readonly [string, string]) - タプルの展開 (
[string, ...string[]]) - タプルのラベル (
[first: string, ...rest: string[]]) - テンプレートリテラル型 (
`foo-${string}`) - ユニオン分配 (
type Distributed = `result: ${'succeeded' | 'failed'}`) - Intrinsic String Manipulation Types (
Uppercase<S>など) -
anyと{} -
unknownとnever - Index Signature (
{ [x: string]: string }) - Indexed Access Types (
FooObject["propName"],BarArray[number]) - Mapped Types (
{ [K in Foo]: string }) - Mapping Modifiers (
{ -readonly [K in keyof Foo]-?: Foo[K] }) - Key Remapping (
{ [K in keyof Foo as `get${Capitalize<string & K>}`]: () => Foo[K] }) - インターフェースの拡張 (
interface Foo extends Bar {}) - インターフェースの Declaration Merging
-
keyof演算子 (type FooKey = keyof Foo)
型と値
-
typeof演算子 (type Foo = typeof foo) - Type Narrowing
- 型ガード関数 (
(v) => v is string) - アサーション関数 (
(v) => asserts v is string) -
asアサーション (foo as unknown as string) - 非 Null アサーション (
foo!) - 明確な割り当てアサーション (
let foo!: string) -
as constアサーション (const foo = [1, 2, 3] as const) -
satisfies演算子 (const foo = ['foo', 'bar', 'baz'] satisfies Foo[])
ジェネリクス
- ジェネリクス型と型引数 (
type Foo<T> = T | undefined) - ジェネリクス関数 (
<T>(input: T) => T | undefined) - ジェネリクスインターフェース (
interface Foo<T> {}) - 型引数の制約 (
type Foo<T extends string>) - デフォルト型引数 (
type Foo<T extends unknown[] = []>) - Conditional Types (
T extends string ? T : never) -
inferによる型の抽出 (T extends [infer F, ...infer R extends unknown[]]) - NoInfer (
NoInfer<T>)
ユーティリティ型/組み込み API
- ユーティリティ型 (
Record<K, V>,Readonly<T>,Pick<T, K>,Parameters<T>など) - 組み込み API の型 (
ReadonlyMap<K, V>,PromiseLike<T>,Generator<T>など)
ヒント/ドキュメンテーション
-
// @ts-ignoreコメントと// @ts-expect-errorコメント -
// @ts-checkコメントと// @ts-nocheckコメント - tsdoc コメント (
/** Comment */)
さらに発展的な機能
- 公称型と構造的部分型
- 余剰プロパティのチェック
unique symbol- this 引数 (
function foo(this: Foo): void {}) - ThisType (
{ methods: M & ThisType<D & M> }) - 変性 (
in,out) - メソッド記法の双変性
- アンビエント宣言 (
declare const foo: unique symbol)
Discussion