TypeScript入門:初心者向け導入ガイド
プログラミングの世界に足を踏み入れたばかりの方や、JavaScriptでの開発経験はあるものの、より堅牢で保守性の高いコードを目指したい方へ。このガイドは、静的型付け言語であるTypeScriptの世界への第一歩を支援するために書かれました。約5000語にわたる詳細な説明を通して、TypeScriptの基本から、なぜそれが現代のWeb開発において重要視されているのかまでを、体系的に理解することを目指します。
このガイドを読み終える頃には、TypeScriptを使った開発環境を構築し、基本的な型システムを理解し、より複雑なアプリケーション開発に自信を持って取り組めるようになっているでしょう。さあ、TypeScriptの旅を始めましょう。
1. はじめに:TypeScriptとは何か、なぜ学ぶのか
現代のソフトウェア開発、特にJavaScriptが主要な言語として使われるWeb開発の世界では、プロジェクトの規模が大きくなるにつれて、コードの管理や保守が難しくなるという課題に直面することが少なくありません。JavaScriptは柔軟性が高く、動的な性質を持つ言語ですが、その柔軟性が時に予期せぬエラーやバグの原因となることもあります。
ここで登場するのがTypeScriptです。
TypeScriptとは何か?
TypeScriptは、Microsoftによって開発・保守されているオープンソースのプログラミング言語です。一言でいうと、「JavaScriptに静的型付けを加えたスーパーセット(上位互換)」です。つまり、有効なJavaScriptコードはすべて有効なTypeScriptコードでもあります。TypeScriptコードは、最終的にブラウザやNode.jsなどのJavaScript実行環境で動作させるために、トランスパイル(Transpile)というプロセスを経てJavaScriptコードに変換されます。この変換を行うためのツールが、TypeScriptコンパイラ(tsc
)です。
なぜTypeScriptを学ぶのか?
JavaScriptで開発を続けることももちろん可能ですが、特に中規模から大規模なアプリケーション開発において、TypeScriptを導入することには多くのメリットがあります。
-
静的型付けによる開発効率と品質の向上:
JavaScriptは動的型付け言語です。これは、変数の型が実行時に決定されることを意味します。例えば、ある変数に最初は文字列、次に数値、その次にオブジェクトを代入するといったことが可能です。これは柔軟ですが、誤った型の値を期待する処理に渡してしまうと、実行時エラーが発生する可能性があります。
一方、TypeScriptは静的型付け言語です。これは、変数の型や関数の引数・戻り値の型をコード記述時(コンパイル前)に指定できます。これにより、型の不一致によるエラーをコンパイル時に発見できます。ちょうど、文章を書いている最中に文法ミスをワープロソフトが指摘してくれるように、コードを書いている最中にエラーを教えてくれるのです。これにより、実行時エラーのリスクを減らし、デバッグにかかる時間を大幅に削減できます。これは開発の効率とコードの品質向上に直接つながります。 -
コードの可読性と保守性の向上:
型情報があることで、その変数や関数がどのようなデータを受け取り、どのようなデータを返すのかが一目瞭然になります。これは、コードを読む他の開発者(あるいは未来の自分自身)にとって非常に大きな助けとなります。特に、チームでの開発や、時間が経ってからコードを修正する必要が出てきた場合に、コードの意図を理解しやすくなります。これにより、保守が容易になり、変更による副作用のリスクを減らすことができます。 -
強力なエディタサポート:
TypeScriptの型情報は、Visual Studio CodeのようなモダンなエディタやIDE(統合開発環境)によってフル活用されます。コード補完(オートコンプリート)、引数の型ヒント、エラーのリアルタイム表示、リファクタリング支援など、開発体験が格段に向上します。これにより、コーディング速度が上がり、タイポや簡単なミスを減らすことができます。 -
大規模開発への適性:
アプリケーションの規模が大きくなり、コードベースが複雑になるほど、型システムがもたらす恩恵は大きくなります。多くのファイル、多くのモジュール、多くの開発者が関わるプロジェクトでは、コード間の依存関係やデータの流れを明確に把握することが重要です。TypeScriptの型システムは、この把握を助け、変更の影響範囲を予測しやすくします。React, Angular, Vue.jsなどの主要なJavaScriptフレームワークもTypeScriptでの開発を強く推奨しており、大規模なエンタープライズアプリケーション開発でTypeScriptが採用されるケースが増えています。
JavaScript経験者へ
JavaScriptの知識はTypeScriptを学ぶ上で非常に大きなアドバンテージとなります。TypeScriptはJavaScriptのスーパーセットであるため、既存のJavaScriptの構文や概念はそのまま使えます。新しく学ぶのは、型システムとその関連機能です。静的型付けの概念に慣れるまで少し戸惑うかもしれませんが、型定義を追加することで、JavaScriptコードがより堅牢になる過程を実感できるでしょう。
プログラミング初心者へ
もしあなたがプログラミング自体を学び始めたばかりであれば、JavaScriptとTypeScriptを同時に学ぶのは少し大変に感じるかもしれません。しかし、TypeScriptの型システムは、データ型というプログラミングの基礎概念を学ぶ上で非常に役立ちます。また、モダンな開発環境ではTypeScriptが標準的に使われていることも多く、最初からTypeScriptに慣れておくことは将来的なキャリアにおいても有利に働く可能性があります。このガイドでは、JavaScriptの基本的な知識も補足しながら説明を進めますので、安心して読み進めてください。
さて、TypeScriptの魅力と学ぶ意義を理解したところで、まずは開発環境を構築し、実際にコードを書いてみましょう。
2. TypeScriptの環境構築
TypeScriptを使い始めるために必要な環境をセットアップします。主に、Node.jsのインストールと、TypeScriptコンパイラのインストールが必要です。
2.1 Node.jsのインストール
TypeScriptコンパイラはNode.js環境上で動作します。ほとんどのモダンなWeb開発ではNode.jsが必須となっているため、まだインストールしていない場合は、まずNode.jsをインストールしてください。
Node.jsの公式ウェブサイト(https://nodejs.org/)にアクセスし、お使いのオペレーティングシステム(Windows, macOS, Linux)に応じたインストーラをダウンロードして実行します。多くの場合、推奨版(LTS – Long-Term Support)をインストールすれば問題ありません。
インストールが完了したら、ターミナル(コマンドプロンプト、PowerShell、iTermなど)を開き、以下のコマンドを実行して、Node.jsとnpm(Node.jsのパッケージマネージャー)が正しくインストールされたか確認します。
bash
node -v
npm -v
それぞれのバージョン番号が表示されれば成功です。
2.2 TypeScriptコンパイラ (tsc) のインストール
Node.jsにはnpm(またはyarnやpnpmなどの代替パッケージマネージャー)が付属しています。これを使ってTypeScriptコンパイラをグローバルにインストールするのが一般的です。
ターミナルで以下のコマンドを実行します。
bash
npm install -g typescript
npm install
はパッケージをインストールするコマンド、-g
はグローバルインストール(システム全体で使用可能にする)を意味します。typescript
はインストールしたいパッケージ名です。
インストールが完了したら、以下のコマンドでTypeScriptコンパイラ(tsc
)が利用できるか確認します。
bash
tsc -v
TypeScriptのバージョン番号が表示されれば、コンパイラのインストールは成功です。
2.3 はじめてのTypeScriptファイルとコンパイル
インストールが完了したので、簡単なTypeScriptファイルを作成してコンパイルしてみましょう。
好きな場所に新しいディレクトリを作成し、その中に移動します。
bash
mkdir my-ts-project
cd my-ts-project
次に、hello.ts
という名前のファイルを作成し、以下のコードを記述します。.ts
という拡張子がTypeScriptファイルを示します。
“`typescript
// my-ts-project/hello.ts
function greet(person: string) {
return “Hello, ” + person;
}
let user = “TypeScript User”;
console.log(greet(user));
// これはコンパイルエラーになります(コメントを外して試してみてください)
// let age = 30;
// console.log(greet(age)); // Argument of type ‘number’ is not assignable to parameter of type ‘string’.
“`
このコードでは、greet
という関数を定義しています。引数person
には: string
という型アノテーションが付いており、「この引数は文字列であるべき」という型情報を示しています。関数は文字列を返すので、TypeScriptは戻り値の型も自動的にstring
と推論します(明示的に: string
と記述することも可能です)。
user
変数には文字列"TypeScript User"
を代入しています。TypeScriptは初期値からuser
変数の型をstring
と推論します。
次に、このhello.ts
ファイルをコンパイルしてJavaScriptファイルに変換します。ターミナルで以下のコマンドを実行します。
bash
tsc hello.ts
このコマンドを実行すると、同じディレクトリ内にhello.js
というJavaScriptファイルが生成されます。ファイルの中身を見てみましょう。
javascript
// my-ts-project/hello.js (tsc hello.ts で生成されるファイル)
function greet(person) {
return "Hello, " + person;
}
var user = "TypeScript User";
console.log(greet(user));
// これはコンパイルエラーになります(コメントを外して試してみてください)
// let age = 30;
// console.log(greet(age)); // Argument of type 'number' is not assignable to parameter of type 'string'.
生成されたJavaScriptコードは、元のTypeScriptコードから型アノテーションなどが取り除かれ、純粋なJavaScript(この場合はES5という古いJavaScriptのバージョン)になっています。
コメントアウトしている部分の型エラーを試すには、hello.ts
の該当行のコメントを外して再度tsc hello.ts
を実行してみてください。ターミナルに以下のようなエラーメッセージが表示されるはずです。
“`
hello.ts:7:20 – error TS2345: Argument of type ‘number’ is not assignable to parameter of type ‘string’.
7 console.log(greet(age));
~~~
Found 1 error in the latest compilation.
“`
このように、TypeScriptコンパイラはコードを実行する前に型エラーを発見して教えてくれます。これがTypeScriptの最大の利点の一つです。
生成されたhello.js
ファイルは、Node.jsを使って実行できます。
bash
node hello.js
実行結果としてHello, TypeScript User
が表示されるはずです。
2.4 tsconfig.jsonについて
小規模なプロジェクトであれば、個別の.ts
ファイルをtsc file.ts
のようにコンパイルするだけでも良いかもしれません。しかし、多くのファイルからなるプロジェクトや、特定のコンパイル設定(出力するJavaScriptのバージョン、どのディレクトリのファイルを対象にするか、どの厳格チェックを有効にするかなど)を指定したい場合は、tsconfig.json
という設定ファイルを使用するのが一般的です。
プロジェクトのルートディレクトリ(my-ts-project
ディレクトリ)で以下のコマンドを実行すると、基本的なtsconfig.json
ファイルが生成されます。
bash
tsc --init
これにより、tsconfig.json
というファイルが生成されます。ファイルの中には多くのオプションがコメントアウトされた状態で含まれています。最初は以下のようないくつかの重要なオプションだけを有効にするか確認しておけば良いでしょう。
“`jsonc
// my-ts-project/tsconfig.json
{
“compilerOptions”: {
/ Language and Environment Options /
“target”: “es2016”, // 生成するJavaScriptのバージョン (例: es5, es2015, es2016, esnext)
// “lib”: [], / Specify a set of bundled library declaration files that describe the target runtime environment. /
// “jsx”: “react”, / Specify what JSX code is generated. /
// “experimentalDecorators”: true, / Enable experimental support for legacy decorators. /
// “emitDecoratorMetadata”: true, / Emit design-type metadata for decorated declarations in source files. /
// “moduleResolution”: “node”, / Specify how modules are resolved. /
// “baseUrl”: “./”, / Specify the base directory to resolve non-relative module names. /
// “paths”: {}, / Specify a set of entries that re-map imports to lookup locations relative to the ‘baseUrl’. /
// “rootDirs”: [], / A list of roots where files are stored. /
// “typeRoots”: [], / Specify multiple folders that act as roots for type definitions. /
// “types”: [], / Specify type package names to be included without being referenced in a source file. /
// “allowSyntheticDefaultImports”: true, / Allow default imports from modules with no default export. This does not affect code emit, just typechecking. /
“esModuleInterop”: true, / Emit additional JavaScript for interoperability between CommonJS and ES modules. /
// “preserveSymlinks”: true, / Disable resolving symlinks to their realpath. This correlates to the same flag in node. /
“forceConsistentCasingInFileNames”: true, / Ensure that casing is correct in imports. /
/* Type Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict checking of null and undefined. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Check calls, binds, and applies on functions. */
// "strictPropertyInitialization": true, /* Check for uninitialized properties in classes. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when a local variable is not read. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "exactOptionalPropertyTypes": true, /* Disallow extra properties from being assigned to objects with optional properties. */
// "noImplicitReturns": true, /* Enable error reporting for functions that lack an explicit return. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to the index signature types of arrays and strings. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors instead of dot access for properties with index signatures. */
// "allowUnusedLabels": true, /* Disable error reporting at unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify how modules are resolved. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": undefined, /* Specify a set of entries that re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": undefined, /* A list of roots where files are stored. */
// "typeRoots": undefined, /* Specify multiple folders that act as roots for type definitions. */
// "types": undefined, /* Specify type package names to be included without being referenced in a source file. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
// "esModuleInterop": true, /* Emit additional JavaScript for interoperability between CommonJS and ES modules. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
// "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip checking the default library declarations files. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
// “include”: [“src//“], / Specify which files to include in the program. */
// “exclude”: [“node_modules”, “/.spec.ts”], / Specify a list of glob patterns that match files to be excluded. /
// “files”: [“core.ts”, “sys.ts”, “types.ts”, “checker.ts”, “commandLineParser.ts”, “emitter.ts”, “program.ts”, “scanner.ts”, “parser.ts”, “utilities.ts”, “identity.ts”] / Specify a list of root files that should be included in the program. */
}
“`
compilerOptions
内で、いくつか重要なオプションを簡単に説明します。
target
: 生成されるJavaScriptのECMAScriptバージョンを指定します。古いブラウザをサポートする場合はes5
やes2015
を選びますが、モダンな環境ではes2016
以降(esnext
も可)を選択することが多いです。module
: 生成されるJavaScriptのモジュールシステムを指定します。Node.jsではcommonjs
が一般的でしたが、最近はesnext
やnode16
などのES Modules形式も増えています。ブラウザ向けではes6
やesnext
が使われます。strict
: このオプションをtrue
にすると、TypeScriptの提供する厳格な型チェックオプションがすべて有効になります。これは強く推奨される設定です。noImplicitAny
,strictNullChecks
など、個別の厳格オプションをまとめて有効にします。esModuleInterop
: CommonJSモジュール(Node.jsでよく使われるrequire()
構文)とES Modules(import
構文)間の相互運用性を高めるためのオプションです。true
にすることを推奨します。outDir
: 生成されるJavaScriptファイルを保存するディレクトリを指定します。例えば"outDir": "./dist"
とすると、dist
ディレクトリに.js
ファイルが出力されます。rootDir
: TypeScriptのソースファイルがあるルートディレクトリを指定します。通常はプロジェクトのルートか、src
ディレクトリなどを指定します。outDir
と組み合わせて使われることが多いです。
tsconfig.json
が存在するディレクトリで単にtsc
コマンドを実行すると、tsconfig.json
の設定に従ってプロジェクト全体のTypeScriptファイルがコンパイルされます。
例えば、tsconfig.json
で"outDir": "./dist"
と設定し、hello.ts
ファイルをsrc
ディレクトリに移動した場合:
my-ts-project/
├── tsconfig.json
└── src/
└── hello.ts
my-ts-project
ディレクトリでtsc
コマンドを実行すると、dist
ディレクトリ内にhello.js
が生成されます。
“`bash
my-ts-projectディレクトリで実行
tsc
“`
これにより、dist
ディレクトリが生成され、その中にsrc/hello.ts
に対応するdist/hello.js
ファイルが作成されます。
my-ts-project/
├── dist/
│ └── hello.js
├── tsconfig.json
└── src/
└── hello.ts
このように、tsconfig.json
はTypeScriptプロジェクトのビルド設定を集中管理するための重要なファイルです。プロジェクトの要件に合わせて適切に設定することが、効率的な開発につながります。
環境構築は以上です。Node.jsとTypeScriptコンパイラがインストールされ、簡単な.ts
ファイルをコンパイルできる状態になりました。次はTypeScriptの核となる「型」について詳しく学んでいきましょう。
3. TypeScriptの基本:型アノテーションと主要な型
TypeScriptの最大の特長は静的型付けです。変数、関数の引数、戻り値などに型アノテーション(Type Annotation)を付けることで、コードの意図を明確にし、コンパイル時に型エラーを検出できます。
3.1 型アノテーション (Type Annotations)
型アノテーションは、変数名や引数名の後ろにコロン :
を付けて型名を記述する形式です。
typescript
let variableName: Type = value;
例:
typescript
let greeting: string = "Hello";
let count: number = 123;
let isLoggedIn: boolean = true;
このように型を明示することで、その変数に代入できる値の型を制限します。例えば、greeting
変数に数値や真偽値を代入しようとすると、TypeScriptコンパイラがエラーを報告します。
typescript
let greeting: string = "Hello";
greeting = 123; // Error: Type 'number' is not assignable to type 'string'.
3.2 プリミティブ型 (Primitive Types)
JavaScriptにも存在する基本的なデータ型です。TypeScriptではこれらの型に対しても型アノテーションを使用します。
string
: 文字列。
typescript
let name: string = "Alice";number
: 数値。整数、浮動小数点数、NaN、Infinityなどを含みます。
typescript
let age: number = 30;
let price: number = 19.99;boolean
: 真偽値。true
またはfalse
。
typescript
let isActive: boolean = false;null
:null
リテラル。
typescript
let data: null = null;-
undefined
:undefined
リテラル。
typescript
let result: undefined = undefined;
デフォルトでは、null
とundefined
は他の型(例えばstring
やnumber
)にも代入可能ですが、tsconfig.json
で"strictNullChecks": true
(または"strict": true
)を有効にすることで、より厳格なチェックが可能になります。これにより、null
やundefined
を意図しない場所で使用することによる実行時エラーを防ぎます。 -
symbol
: ES2015で導入された、一意で不変のプリミティブ値。
typescript
const sym: symbol = Symbol("description"); bigint
: ES2020で導入された、非常に大きな整数を扱える型。
typescript
const bigNumber: bigint = 100n;
3.3 配列の型
配列の型は、要素の型の後ろに[]
を付けて表現します。
typescript
let numbers: number[] = [1, 2, 3, 4, 5];
let names: string[] = ["Alice", "Bob", "Charlie"];
または、ジェネリクス構文を使って表現することもできます。
typescript
let numbers: Array<number> = [1, 2, 3, 4, 5];
どちらの書き方も同じ意味ですが、number[]
の方が一般的によく使われます。
複数の型の要素を含む可能性がある配列を表現する場合は、後述するユニオン型を使います。
3.4 タプル (Tuple)
タプルは、要素の数とそれぞれの要素の型が固定された配列のようなものです。異なる型の要素を、決まった順序で格納する場合に便利です。
“`typescript
// string, number, boolean の順で3つの要素を持つタプル
let user: [string, number, boolean] = [“Alice”, 30, true];
console.log(user[0]); // “Alice” (型は string)
console.log(user[1]); // 30 (型は number)
// 誤った型の要素を代入しようとするとエラー
// user[0] = 123; // Error: Type ‘number’ is not assignable to type ‘string’.
// 要素数を無視して新しい要素を追加することはできますが、
// その要素へのアクセスは型安全ではありません。
// user.push(“additional item”); // pushは許可される場合があるが推奨されない
// console.log(user[3]); // undefined (型は string | number | boolean)
“`
タプルは、関数の戻り値で複数の値を返す場合や、固定フォーマットのデータを扱う場合に役立ちます。
3.5 Any型
any
型は、あらゆる型の値を許容します。TypeScriptの型チェックを完全に無効にしたい場合に使用します。
“`typescript
let data: any = “Hello”;
data = 123;
data = true;
data = { name: “Alice” };
let unknownValue: any = data;
unknownValue.method(); // エラーにならないが、実行時にエラーになる可能性がある
“`
any
型を使うと、TypeScriptの型チェックの恩恵を受けられなくなります。これはJavaScriptを使っているのとほぼ同じ状態です。TypeScriptを導入する最大の目的は型安全性であるため、可能な限りany
型の使用は避けるべきです。特に、ライブラリから返される値や、動的なデータ構造を扱う場合に安易にany
を使うのではなく、より具体的な型を検討することが重要です。
3.6 Unknown型
unknown
型は、any
型に似ていますが、より型安全です。unknown
型の変数にはあらゆる型の値を代入できますが、その値に対して何らかの操作(プロパティへのアクセス、メソッドの呼び出しなど)を行うには、事前にその型を絞り込む(Narrowing)必要があります。
“`typescript
let value: unknown;
value = “Hello”;
value = 123;
// unknown型に対して直接操作しようとするとエラーになる
// console.log(value.toFixed(2)); // Error: ‘value’ is of type ‘unknown’.
// 型の絞り込みを行う必要がある
if (typeof value === ‘number’) {
console.log(value.toFixed(2)); // OK: valueは number 型に絞り込まれた
}
“`
unknown
型は、APIから受け取ったデータや、ユーザー入力など、実行時になるまで型がわからない値を扱う場合に非常に有用です。any
よりもunknown
を使う方が、意図しない型のエラーを防ぐためにより安全です。
3.7 Void型
void
型は、関数が値を返さないことを示します。JavaScriptでは、値を返さない関数は暗黙的にundefined
を返しますが、TypeScriptではvoid
型を使用します。
typescript
function logMessage(message: string): void {
console.log(message);
// return; // これはOK
// return undefined; // strictNullChecks が false なら OK
// return "Hello"; // Error
}
3.8 Never型
never
型は、決して戻ることがない関数の戻り値の型や、決して到達しないコードポイントの型を表します。例えば、常に例外をスローする関数や、無限ループする関数などがnever
型を戻り値として持ちます。
“`typescript
function error(message: string): never {
throw new Error(message);
}
function infiniteLoop(): never {
while (true) {
// …
}
}
// never型は他の型に代入できない(anyを除く)
// let result: string = error(“Something went wrong”); // Error
“`
never
型は、網羅性チェックなどで役立ちます。例えば、ユニオン型のすべての可能性を処理しきれていない場合に、never
型変数への代入エラーとして検出できます。
3.9 NullとUndefined (strictNullChecks
オプション)
前述の通り、null
とundefined
はJavaScriptのプリミティブ型です。TypeScriptでは、tsconfig.json
で"strictNullChecks": true
(または"strict": true
)を設定することが強く推奨されます。
このオプションを有効にすると、null
とundefined
はそれぞれの型(null
型、undefined
型)以外の変数にはデフォルトで代入できなくなります。変数をnull
またはundefined
も取りうる型として宣言するには、ユニオン型を使用します。
“`typescript
let name: string;
name = null; // Error: Type ‘null’ is not assignable to type ‘string’. (strictNullChecks: trueの場合)
let nameOrNull: string | null; // string または null
nameOrNull = “Alice”; // OK
nameOrNull = null; // OK
// nameOrNull = 123; // Error
“`
これにより、null
やundefined
の可能性がある値に対して、それらを適切にチェックせずにアクセスすることによる実行時エラー(例: "hello".length
はOKだが、null.length
は実行時エラー)を防ぐことができます。
3.10 オブジェクトの型
JavaScriptでは、配列以外のほとんどの非プリミティブ値はオブジェクトです。TypeScriptでは、オブジェクトの構造を型として定義できます。
最も基本的なオブジェクトの型定義は、プロパティ名とその型を波括弧 {}
で囲んで記述する方法です。
“`typescript
let person: { name: string; age: number };
person = { name: “Bob”, age: 25 };
// person = { name: “Charlie” }; // Error: Property ‘age’ is missing in type ‘{ name: string; }’ but required in type ‘{ name: string; age: number; }’.
// person = { name: “David”, age: “thirty” }; // Error: Type ‘string’ is not assignable to type ‘number’.
“`
オブジェクトの型定義内で、プロパティ名の後ろに ?
を付けると、そのプロパティは省略可能(Optional)になります。
“`typescript
let car: { make: string; model: string; year?: number };
car = { make: “Toyota”, model: “Corolla” }; // OK (yearは省略可能)
car = { make: “Honda”, model: “Civic”, year: 2022 }; // OK
“`
プロパティ名の前に readonly
を付けると、そのプロパティは読み取り専用になります。初期化時以外は値を変更できません。
“`typescript
let point: { readonly x: number; readonly y: number };
point = { x: 10, y: 20 };
// point.x = 5; // Error: Cannot assign to ‘x’ because it is a read-only property.
“`
オブジェクトの型定義は、後述するインターフェース(Interface)や型エイリアス(Type Alias)を使って名前を付けて再利用可能にすることができます。
3.11 関数 (Functions)
関数の定義においても、引数と戻り値に型アノテーションを付けることで、型安全性を確保できます。
“`typescript
// 引数と戻り値の型を指定
function add(x: number, y: number): number {
return x + y;
}
// add(“hello”, 5); // Error: Argument of type ‘string’ is not assignable to parameter of type ‘number’.
// 戻り値の型を省略した場合、TypeScriptは推論する
function multiply(x: number, y: number) {
return x * y; // 戻り値の型は number と推論される
}
// 値を返さない関数の戻り値は void
function logMessage(message: string): void {
console.log(message);
}
“`
3.11.1 省略可能な引数 (Optional Parameters)
引数名の後ろに ?
を付けることで、その引数を省略可能にできます。省略された引数の型は、自動的に undefined
とのユニオン型になります(T | undefined
)。
“`typescript
function buildName(firstName: string, lastName?: string): string {
if (lastName) {
return firstName + ” ” + lastName;
} else {
return firstName;
}
}
buildName(“Bob”); // OK
buildName(“Bob”, “Adams”); // OK
// buildName(“Bob”, “Adams”, “Sr.”); // Error: Expected 1-2 arguments, but got 3.
“`
省略可能な引数は、必須の引数の後に記述する必要があります。
3.11.2 デフォルト引数 (Default Parameters)
引数にデフォルト値を設定できます。デフォルト値を持つ引数は、省略された場合にそのデフォルト値が使われます。デフォルト引数も、省略可能な引数と同様に、必須の引数の後に記述するのが一般的ですが、先頭に置いて省略可能にし、デフォルト値を持つという特殊な使い方も可能です。
“`typescript
function buildNameWithDefault(firstName: string, lastName: string = “Smith”): string {
return firstName + ” ” + lastName;
}
buildNameWithDefault(“Bob”); // OK, lastNameは “Smith” になる -> “Bob Smith”
buildNameWithDefault(“Bob”, “Adams”); // OK, lastNameは “Adams” になる -> “Bob Adams”
“`
デフォルト引数に型アノテーションを付けることもできますが、デフォルト値から型を推論できる場合は省略しても構いません。
3.11.3 レストパラメータ (Rest Parameters)
関数が不定数の引数を受け取る場合、レストパラメータを使用できます。レストパラメータは常に配列であり、引数リストの最後に記述します。
“`typescript
function sumAll(a: number, b: number, …restOfNumbers: number[]): number {
let sum = a + b;
for (const num of restOfNumbers) {
sum += num;
}
return sum;
}
sumAll(1, 2); // 3
sumAll(1, 2, 3, 4, 5); // 15
“`
レストパラメータの型は、要素の型の配列(例: number[]
)として指定します。
3.11.4 関数型の定義
関数自体の型を定義することも可能です。これは、変数に関数を代入したり、関数を別の関数の引数として渡したりする場合に便利です。
関数型は、引数の型リストと戻り値の型を矢印 =>
でつないで記述します。
“`typescript
let myGreet: (person: string) => string;
myGreet = function(name: string) {
return “Hello, ” + name;
};
// myGreet = function(age: number) { // Error: Type ‘(age: number) => string’ is not assignable to type ‘(person: string) => string’.
// return “Hello, ” + age;
// };
“`
3.12 オブジェクトとインターフェース (Objects and Interfaces)
前述のオブジェクトの型定義を、再利用可能な名前付きの型として定義する方法がインターフェースです。インターフェースは、オブジェクトが持つべきプロパティやメソッドの構造を定義するために使用されます。
3.12.1 インターフェース (Interface) の定義
interface
キーワードを使って定義します。
“`typescript
interface Person {
name: string;
age: number;
// 省略可能なプロパティ
country?: string;
// 読み取り専用プロパティ
readonly id: number;
}
// インターフェースに適合するオブジェクトを宣言
let user: Person = {
id: 1,
name: “Alice”,
age: 30
};
// user.id = 2; // Error: Cannot assign to ‘id’ because it is a read-only property.
// let user2: Person = { name: “Bob” }; // Error: Property ‘age’ is missing…
“`
インターフェースは、クラスが特定の構造を持つことを強制するためにも使用されます(後述)。
3.12.2 インデックスシグネチャ (Index Signatures)
オブジェクトが固定の名前のプロパティだけでなく、動的な名前のプロパティを多数持つ可能性がある場合、インデックスシグネチャを使用してその構造を表現できます。例えば、キーが文字列で値が数値の辞書のようなオブジェクトなどです。
“`typescript
interface StringDictionary {
[key: string]: number; // キーは string 型、値は number 型
}
let scores: StringDictionary = {};
scores[“math”] = 95;
scores[“science”] = 88;
// scores[“english”] = “excellent”; // Error: Type ‘string’ is not assignable to type ‘number’.
“`
キーの型はstring
またはnumber
である必要があります。
3.12.3 インターフェースの継承 (Interface Inheritance)
インターフェースは他のインターフェースを継承(拡張)できます。これにより、既存のインターフェースの定義を再利用し、新しいプロパティを追加してより具体的なインターフェースを作成できます。
“`typescript
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
let myDog: Dog = {
name: “Buddy”,
breed: “Golden Retriever”
};
// let invalidDog: Dog = { name: “Max” }; // Error: Property ‘breed’ is missing…
“`
複数のインターフェースを継承することも可能です。
“`typescript
interface Speakable {
speak(): void;
}
interface Walkable {
walk(): void;
}
interface ActionAnimal extends Animal, Speakable, Walkable {
// Animal, Speakable, Walkable のすべてのプロパティとメソッドを持つ
}
let pet: ActionAnimal = {
name: “Leo”,
speak: () => console.log(“Woof!”),
walk: () => console.log(“Walking…”)
};
“`
3.13 型エイリアス (Type Aliases)
型エイリアスは、既存の型に新しい名前を付けるために使用されます。type
キーワードを使用します。プリミティブ型、ユニオン型、インターセクション型、タプル、関数型、そしてオブジェクト型など、あらゆる型に対してエイリアスを付けることができます。
“`typescript
// プリミティブ型へのエイリアス
type MyString = string;
let username: MyString = “Setter”;
// オブジェクト型へのエイリアス
type Point = {
x: number;
y: number;
};
let coordinate: Point = { x: 10, y: 20 };
// ユニオン型へのエイリアス
type Id = number | string;
let userId: Id = 123;
let productId: Id = “abc-456”;
// 関数型へのエイリアス
type GreetFunction = (name: string) => string;
const sayHello: GreetFunction = (personName) => {
return Hello, ${personName}!
;
};
// タプル型へのエイリアス
type RgbColor = [number, number, number];
const red: RgbColor = [255, 0, 0];
“`
インターフェースと型エイリアスの使い分け
インターフェースと型エイリアスは、オブジェクトの型を定義する上で似たような用途で使えます。どちらを使うべきか、しばしば議論になります。一般的な使い分けのガイドラインは以下の通りです。
- オブジェクトやクラスの公開APIの型を定義する場合: インターフェースが推奨されることが多いです。インターフェースは「拡張(
extends
)」や「実装(implements
)」という概念と相性が良く、オブジェクト指向的な構造を表現するのに適しています。また、同じ名前のインターフェースは自動的にマージされる(Declaration Merging)という特性があり、これはライブラリの型定義などで役立ちます。 - プリミティブ型、ユニオン型、タプル、関数型など、オブジェクト型以外の型に名前を付ける場合: 型エイリアスを使います。インターフェースはオブジェクト型の定義に特化しています。
- 複雑な型定義(ユニオンやインターセクションの組み合わせなど)に名前を付けて可読性を上げたい場合: 型エイリアスが適しています。
どちらを使っても大差ない場合も多いですが、一貫性を保つことが重要です。プロジェクト内でどちらかに統一する、あるいは特定の用途で使い分けるルールを設けると良いでしょう。
4. TypeScriptの発展的な概念
基本の型システムを理解したら、TypeScriptのより強力な機能を見ていきましょう。
4.1 ユニオン型 (Union Types) とインターセクション型 (Intersection Types)
複数の型を組み合わせることで、より柔軟かつ正確な型を表現できます。
4.1.1 ユニオン型 (Union Types)
ユニオン型は、変数が複数の型のうちのいずれか一つを取りうることを示します。|
演算子を使って型を組み合わせます。
“`typescript
// string または number 型
let id: number | string;
id = 123; // OK
id = “abc”; // OK
// id = true; // Error
// 複数のオブジェクト型を組み合わせる
interface Bird {
fly(): void;
layEggs(): void;
}
interface Fish {
swim(): void;
layEggs(): void;
}
type Pet = Bird | Fish; // Bird または Fish 型
function getSmallPet(): Pet {
// … どちらかを返すロジック
return { fly: () => console.log(“flying”), layEggs: () => console.log(“laying eggs”) }; // 例としてBirdを返す
}
let pet = getSmallPet();
// pet.fly(); // OK (Bird型であれば存在する)
// pet.swim(); // Error: Property ‘swim’ does not exist on type ‘Pet’. Property ‘swim’ does not exist on type ‘Bird’.
“`
ユニオン型の値に対してアクセスできるプロパティやメソッドは、そのユニオン型に含まれるすべての型に共通して存在するものだけです。上記の例でpet.fly()
やpet.swim()
に直接アクセスできないのはそのためです。
4.1.2 型の絞り込み (Type Narrowing)
ユニオン型の場合、変数が実際にどの型であるかは実行時までわかりません。しかし、コードの中で特定の条件に基づいて変数の型を絞り込むことができます。これを型の絞り込み (Type Narrowing) と呼びます。TypeScriptコンパイラは、コードの構造(if
文、typeof
、instanceof
など)を解析して、変数の型をより具体的なものに特定します。
typeof
演算子: プリミティブ型に対して使用します。
typescript
function printId(id: number | string) {
if (typeof id === 'string') {
// id の型は string に絞り込まれる
console.log(id.toUpperCase());
} else {
// id の型は number に絞り込まれる
console.log(id.toFixed(2));
}
}-
instanceof
演算子: クラスのインスタンスに対して使用します。
“`typescript
class Animal {
name: string;
constructor(name: string) { this.name = name; }
}class Dog extends Animal {
bark() { console.log(“Woof!”); }
}class Cat extends Animal {
meow() { console.log(“Meow!”); }
}type PetAnimal = Dog | Cat;
function greetPet(pet: PetAnimal) {
if (pet instanceof Dog) {
// pet の型は Dog に絞り込まれる
pet.bark();
} else {
// pet の型は Cat に絞り込まれる
pet.meow();
}
}
* **`in` 演算子:** オブジェクトに特定のプロパティが存在するかどうかで絞り込みます。
typescript
interface Bird {
fly(): void;
layEggs(): void;
}interface Fish {
swim(): void;
layEggs(): void;
}type Pet = Bird | Fish;
function isFish(pet: Pet): pet is Fish { // 型述語 (Type Predicate) については後述
return (pet as Fish).swim !== undefined; // または ‘swim’ in pet
}function playWithPet(pet: Pet) {
if (‘swim’ in pet) {
// pet の型は Fish に絞り込まれる
pet.swim();
} else {
// pet の型は Bird に絞り込まれる
pet.fly();
}
pet.layEggs(); // layEggs はどちらの型にもあるので常にアクセス可能
}
* **等価性チェック:** 特定のリテラル値との比較でも絞り込みが行われます。
typescript
type Status = “success” | “error”;function processStatus(status: Status) {
if (status === “success”) {
// status の型は “success” (リテラル型) に絞り込まれる
console.log(“処理成功”);
} else {
// status の型は “error” (リテラル型) に絞り込まれる
console.log(“処理失敗”);
}
}
* **判別可能なユニオン (Discriminated Unions):** オブジェクトのユニオン型において、共通のプロパティ(判別子、Discriminant)の値によって型を区別する強力な手法です。
typescript
interface Circle {
kind: “circle”; // 判別子プロパティ
radius: number;
}interface Square {
kind: “square”; // 判別子プロパティ
sideLength: number;
}type Shape = Circle | Square;
function getArea(shape: Shape) {
if (shape.kind === “circle”) {
// shape の型は Circle に絞り込まれる
return Math.PI * shape.radius ** 2;
} else { // ここでは shape.kind が “square” であると判断される
// shape の型は Square に絞り込まれる
return shape.sideLength ** 2;
}
}
“`
判別可能なユニオンは、状態やイベントの種類などを表現する際によく使用されます。
4.1.3 インターセクション型 (Intersection Types)
インターセクション型は、複数の型を一つに結合します。&
演算子を使って型を組み合わせます。結合された型は、元になったすべての型が持つプロパティやメソッドをすべて持つことになります。
“`typescript
interface Employee {
id: number;
department: string;
}
interface Manager {
manage: (employee: Employee) => void;
}
// Employee と Manager の両方のプロパティとメソッドを持つ型
type BusinessLeader = Employee & Manager;
let leader: BusinessLeader = {
id: 101,
department: “Sales”,
manage: (emp) => {
console.log(Managing employee ${emp.id}
);
}
};
// leader.id にアクセス可能 (Employeeから)
// leader.department にアクセス可能 (Employeeから)
// leader.manage() にアクセス可能 (Managerから)
“`
インターセクション型は、既存の型を組み合わせて新しい型を作成する際に便利です。Mixinのような概念を型レベルで表現するのに役立ちます。
4.2 Enum (Enumerations)
Enumは、関連する定数群に名前を付けて管理するための機能です。数値Enumと文字列Enumがあります。
4.2.1 数値Enum
“`typescript
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
let moving: Direction = Direction.Up;
console.log(moving); // 0
// 値を指定することも可能
enum Status {
Pending = 1,
Approved = 2,
Rejected = 4 // 連続していなくても良い
}
let currentStatus: Status = Status.Approved;
console.log(currentStatus); // 2
console.log(Status[4]); // “Rejected” – 値から名前を取得することも可能 (双方向マッピング)
“`
4.2.2 文字列Enum
文字列Enumは、より可読性が高く、デバッグしやすい傾向があります。双方向マッピングは行われません。
“`typescript
enum HttpMethod {
Get = “GET”,
Post = “POST”,
Put = “PUT”,
Delete = “DELETE”
}
let method: HttpMethod = HttpMethod.Get;
console.log(method); // “GET”
// console.log(HttpMethod[“GET”]); // undefined (文字列Enumには双方向マッピングはない)
“`
Enumは、特定の選択肢の中から値を選ぶ必要がある場合に、マジックナンバーやマジックストリングを避けてコードの可読性を高めるのに役立ちます。
4.3 Generics (ジェネリクス)
ジェネリクスは、型をパラメータとして扱えるようにする機能です。これにより、様々な型のデータを扱える再利用可能なコンポーネント(関数、クラス、インターフェースなど)を作成しつつ、型の安全性も同時に確保できます。
4.3.1 なぜジェネリクスが必要か
ジェネリクスがない場合、様々な型を扱える関数を書くにはany
型を使うか、型ごとにオーバーロードするしかありません。
any
を使う場合:
“`typescript
function identityAny(arg: any): any {
return arg;
}
let outputAny = identityAny(“myString”); // outputAny は any 型
console.log(outputAny.length); // エラーにならないが、実行時エラーの可能性がある
“`
この場合、関数の引数と戻り値の型が関連付けられていません。どんな型の値を渡しても、戻り値はany
になってしまいます。
オーバーロードする場合:
“`typescript
function identity(arg: string): string;
function identity(arg: number): number;
function identity(arg: any): any { // 実装シグネチャは any にする必要がある
return arg;
}
let outputString = identity(“myString”); // outputString は string 型
console.log(outputString.length); // OK
let outputNumber = identity(123); // outputNumber は number 型
// console.log(outputNumber.length); // Error
“`
これは型安全ですが、サポートしたい型が増えるたびにオーバーロードを増やす必要があり、冗長です。
ジェネリクスを使うと、これをより簡潔かつ型安全に表現できます。
4.3.2 ジェネリック関数
関数名の後ろに山括弧 <>
を使い、型変数(Type Variable)を宣言します。慣習的にT
(TypeのT)がよく使われますが、U
, K
, V
, Args
, Props
など、意味が分かりやすい名前を使うこともあります。
“`typescript
function identity
return arg;
}
// 明示的に型を指定する場合
let output1 = identity
console.log(output1.length); // OK
// 型推論に任せる場合(ほとんどの場合、推論で十分)
let output2 = identity(123); // T は number に推論される
// console.log(output2.length); // Error
let output3 = identity(true); // T は boolean に推論される
“`
このように、T
という型変数は、関数を呼び出す際に指定された(または推論された)具体的な型に置き換えられます。これにより、引数と戻り値の型を「同じ型である」という関連付けを維持したまま、様々な型を扱える関数を作成できます。
4.3.3 ジェネリックインターフェース
インターフェースもジェネリックにできます。これは、リストや辞書などのデータ構造の型を定義する際によく使用されます。
“`typescript
interface Box
value: T;
}
let stringBox: Box
let numberBox: Box
// stringBox.value = 456; // Error: Type ‘number’ is not assignable to type ‘string’.
“`
4.3.4 ジェネリッククラス
クラスもジェネリックにできます。
“`typescript
class Queue
private elements: T[] = [];
enqueue(element: T): void {
this.elements.push(element);
}
dequeue(): T | undefined {
return this.elements.shift();
}
}
let stringQueue = new Queue
stringQueue.enqueue(“apple”);
stringQueue.enqueue(“banana”);
// stringQueue.enqueue(123); // Error
let item1 = stringQueue.dequeue(); // item1 は string 型
console.log(item1?.toUpperCase()); // OK
let numberQueue = new Queue
numberQueue.enqueue(10);
numberQueue.enqueue(20);
let item2 = numberQueue.dequeue(); // item2 は number 型
// console.log(item2?.toUpperCase()); // Error
“`
4.3.5 型制約 (Type Constraints)
ジェネリック型変数に対して、特定のプロパティを持つ型や特定のインターフェースを実装する型に制限したい場合があります。これにはextends
キーワードを使用します。
例えば、「length
プロパティを持つ型」に制限したい場合:
“`typescript
interface Lengthwise {
length: number;
}
function loggingIdentity
console.log(arg.length); // arg は Lengthwise を拡張しているので、length プロパティへのアクセスが保証される
return arg;
}
loggingIdentity(“hello”); // OK (string は length プロパティを持つ)
loggingIdentity([1, 2, 3]); // OK (array は length プロパティを持つ)
// loggingIdentity(3); // Error: Argument of type ‘number’ is not assignable to parameter of type ‘Lengthwise’.
// loggingIdentity({ value: 10 }); // Error: Argument of type ‘{ value: number; }’ is not assignable to parameter of type ‘Lengthwise’.
“`
T extends Lengthwise
は、「型変数T
はLengthwise
インターフェース(またはその構造を持つ型)を拡張(満たす)しなければならない」という意味になります。
4.4 クラス (Classes)
TypeScriptは、ES6クラス構文を完全にサポートしており、さらに静的型付けやアクセス修飾子などの機能を追加しています。
“`typescript
class Greeter {
// プロパティの型宣言
greeting: string;
// コンストラクタ
constructor(message: string) {
this.greeting = message;
}
// メソッド
greet(): string {
return “Hello, ” + this.greeting;
}
}
let greeter = new Greeter(“world”);
console.log(greeter.greet()); // “Hello, world”
“`
4.4.1 アクセス修飾子 (Access Modifiers)
TypeScriptでは、クラスのメンバー(プロパティやメソッド)に対してpublic
, private
, protected
, readonly
というアクセス修飾子を付けて、クラス外部からのアクセスレベルを制御できます。
public
: デフォルト。クラス内外どこからでもアクセス可能。private
: そのクラスの内部からのみアクセス可能。protected
: そのクラス自身と、そのクラスを継承したサブクラスからアクセス可能。readonly
: プロパティにのみ使用。初期化時以外は値を変更できない(前述のオブジェクト型でも使用)。
“`typescript
class Animal {
public name: string;
private age: number;
protected species: string;
readonly id: number;
constructor(name: string, age: number, species: string, id: number) {
this.name = name;
this.age = age;
this.species = species;
this.id = id;
}
public getAge(): number {
return this.age; // private メンバーへのアクセスはクラス内部からOK
}
}
class Dog extends Animal {
bark() {
console.log(${this.name} (${this.species}) says Woof!
);
// console.log(this.age); // Error: private メンバーへのアクセスはサブクラスからNG
console.log(this.species); // OK: protected メンバーへのアクセスはサブクラスからOK
}
}
let animal = new Animal(“Generic Animal”, 5, “Mammal”, 1);
console.log(animal.name); // OK (public)
// console.log(animal.age); // Error (private)
// console.log(animal.species); // Error (protected)
console.log(animal.getAge()); // OK (public メソッド経由)
// animal.id = 2; // Error (readonly)
“`
コンストラクタの引数に直接アクセス修飾子を付けることで、プロパティ宣言と初期化を同時に行うことができます。
“`typescript
class AnimalShorthand {
// constructorの引数にアクセス修飾子を付けると、
// 同名のプロパティが生成され、引数の値で初期化される
constructor(public name: string, private age: number) {
// this.name = name; // この行は不要になる
// this.age = age; // この行も不要になる
}
getAge() {
return this.age;
}
}
“`
4.4.2 抽象クラス (Abstract Classes)
抽象クラスは、それ自体をインスタンス化することはできず、他のクラスに継承されることを目的としたクラスです。抽象クラスは、抽象メソッド(実装を持たないメソッド)を持つことができます。抽象メソッドを持つクラスは必ず抽象クラスとして宣言する必要があります。
“`typescript
abstract class Shape {
abstract getArea(): number; // 抽象メソッド (実装なし)
// 抽象クラスは具象メソッドも持つことができる
printArea() {
console.log(Area: ${this.getArea()}
);
}
}
// const shape = new Shape(); // Error: Cannot create an instance of an abstract class.
class Circle extends Shape {
constructor(public radius: number) {
super(); // 抽象クラスを継承する場合は super() を呼び出す
}
// 抽象メソッドを実装する必要がある
getArea(): number {
return Math.PI * this.radius ** 2;
}
}
class Rectangle extends Shape {
constructor(public width: number, public height: number) {
super();
}
// 抽象メソッドを実装する必要がある
getArea(): number {
return this.width * this.height;
}
}
let circle = new Circle(5);
circle.printArea(); // Area: 78.53…
let rectangle = new Rectangle(4, 6);
rectangle.printArea(); // Area: 24
“`
抽象クラスは、共通の基盤となる構造を定義し、サブクラスに特定の実装を強制したい場合に有用です。
4.4.3 インターフェースの実装 (Implementing Interfaces)
クラスはインターフェースを実装(implements
)することで、そのインターフェースで定義されたプロパティやメソッドを持つことを保証できます。
“`typescript
interface Greetable {
greeting: string;
greet(message: string): string;
}
class MyGreeter implements Greetable {
greeting: string; // インターフェースで定義されたプロパティを実装
constructor(message: string) {
this.greeting = message;
}
greet(message: string): string { // インターフェースで定義されたメソッドを実装
return ${this.greeting}, ${message}!
;
}
}
let myObject: Greetable = new MyGreeter(“Hello”);
console.log(myObject.greet(“World”)); // “Hello, World!”
“`
一つのクラスが複数のインターフェースを実装することも可能です。
“`typescript
interface Disposable {
dispose(): void;
}
interface Runnable {
run(): void;
}
class MyResource implements Disposable, Runnable {
dispose(): void {
console.log(“Resource disposed.”);
}
run(): void {
console.log(“Resource running.”);
}
}
“`
インターフェースは、クラスがどのような「契約」(つまり、どのようなプロパティやメソッドを持つべきか)を満たすべきかを定義する強力なツールです。
4.5 モジュール (Modules)
現代のJavaScript開発では、コードを小さなファイル(モジュール)に分割し、必要に応じてインポート/エクスポートするのが一般的です。TypeScriptはES Modulesの構文(import
/export
)をサポートしています。
モジュールを使用するには、tsconfig.json
で"module"
オプションを適切に設定する必要があります(例: "module": "esnext"
, "module": "commonjs"
など)。また、通常は"outDir"
オプションも設定し、生成されるJavaScriptファイルを特定のディレクトリにまとめます。
例:
src/printer.ts
ファイル:
``typescript
— ${message} —`);
// src/printer.ts
export function printMessage(message: string): void {
console.log(
}
export const appName = “My App”;
// この関数はモジュールの内部でのみ使用可能 (exportされていないため)
function internalHelper(): void {
console.log(“This is an internal helper.”);
}
“`
src/main.ts
ファイル:
“`typescript
// src/main.ts
import { printMessage, appName } from “./printer”;
// import { internalHelper } from “./printer”; // Error: Module ‘”./printer”‘ has no exported member ‘internalHelper’.
printMessage(Starting ${appName}...
);
printMessage(“Operation complete.”);
“`
tsconfig.json
で rootDir
, outDir
, module
, target
などを設定し、tsc
を実行すると、対応するJavaScriptファイルが生成されます。
json
// tsconfig.json
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs", // または esnext など
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true
}
}
tsc
コマンドを実行すると、dist
ディレクトリ内に printer.js
と main.js
が生成されます。
dist/printer.js
(commonjsの場合):
javascript
// dist/printer.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.appName = exports.printMessage = void 0;
function printMessage(message) {
console.log(`--- ${message} ---`);
}
exports.printMessage = printMessage;
exports.appName = "My App";
// この関数はモジュールの内部でのみ使用可能 (exportされていないため)
function internalHelper() {
console.log("This is an internal helper.");
}
dist/main.js
(commonjsの場合):
javascript
// dist/main.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const printer_1 = require("./printer");
(0, printer_1.printMessage)(`Starting ${printer_1.appName}...`);
(0, printer_1.printMessage)("Operation complete.");
Node.js環境であれば、node dist/main.js
で実行できます。ブラウザ環境であれば、HTMLファイルからモジュールとして読み込むか、WebpackやRollupなどのバンドラーを使って一つのファイルにまとめる必要があります。
デフォルトエクスポート (Default Export)
モジュールあたり一つの主要なエクスポートがある場合、デフォルトエクスポートを使用できます。
src/calculator.ts
:
typescript
// src/calculator.ts
export default class Calculator {
add(x: number, y: number): number {
return x + y;
}
subtract(x: number, y: number): number {
return x - y;
}
}
src/app.ts
:
“`typescript
// src/app.ts
import MyCalculator from “./calculator”; // 名前は任意に付けられる
const calc = new MyCalculator();
console.log(calc.add(5, 3)); // 8
“`
5. 型推論 (Type Inference)
TypeScriptの強力な機能の一つに、型推論があります。これは、開発者が明示的に型アノテーションを指定しなくても、TypeScriptコンパイラがコードから自動的に変数の型や関数の戻り値の型などを推測する機能です。
例えば:
“`typescript
let greeting = “Hello, TypeScript”; // 初期値が文字列なので、greeting は string 型と推論される
// greeting = 123; // Error: Type ‘number’ is not assignable to type ‘string’.
let count = 100; // 初期値が数値なので、count は number 型と推論される
// count = “one hundred”; // Error
const isDone = false; // const で宣言されたプリミティブはリテラル型に推論されることが多い
// isDone = true; // Error: Cannot assign to ‘isDone’ because it is a read-only property.
let numbers = [1, 2, 3]; // 要素がすべて number なので、numbers は number[] 型と推論される
// numbers.push(“four”); // Error: Argument of type ‘string’ is not assignable to parameter of type ‘number’.
let mixedArray = [1, “hello”, true]; // 要素に複数の型が含まれるため、(number | string | boolean)[] 型と推論される
mixedArray.push(false); // OK
mixedArray.push(123); // OK
mixedArray.push(“world”); // OK
function add(x: number, y: number) {
return x + y; // 引数の型から、戻り値は number 型と推論される
}
let result = add(5, 10); // result は number 型と推論される
// result.toUpperCase(); // Error
“`
型推論があるため、すべての場所に型アノテーションを記述する必要はありません。これにより、TypeScriptコードは冗長になりすぎず、JavaScriptに近い感覚で記述できます。
型推論に頼るべきか、明示的に型アノテーションを付けるべきか
これは判断が分かれるところですが、一般的には以下のガイドラインが考えられます。
-
型推論に任せる場合:
- 変数の初期値が明確で、型が自明な場合(例:
let count = 0;
)。 - 関数の戻り値の型が、引数や関数本体から明らかである場合(ただし、複雑なロジックの場合は明示的に記述する方が分かりやすいこともあります)。
- コードの可読性を損なわない場合。
- 変数の初期値が明確で、型が自明な場合(例:
-
明示的に型アノテーションを付ける場合:
- 関数の引数(これはほぼ必須と考えるべきです)。
- 関数の戻り値が複雑な型である場合、またはコードの意図を明確にしたい場合。
- 変数の初期値がない場合(例:
let data: string;
)。 - ユニオン型やインターセクション型など、推論が複雑になる場合。
- コードを読む他の開発者にとって、型を明確に示したい場合。
- 特に
strictNullChecks
が有効な場合に、変数にnull
やundefined
が含まれる可能性を示す場合。
tsconfig.json
で"noImplicitAny": true
(または"strict": true
)を有効にすると、型推論でany
型になった場合にエラーを報告するようになります。これは、意図せず型チェックの恩恵を受けられなくなる状況を防ぐために非常に有用であり、通常は有効にすることが推奨されます。
6. 型定義ファイル (Declaration Files) – .d.ts
TypeScriptの大きな利点の一つは、既存のJavaScriptライブラリやフレームワークと連携できることです。しかし、JavaScriptライブラリには型情報がありません。TypeScriptでこれらのライブラリを型安全に利用するためには、ライブラリの構造(関数名、クラス名、プロパティ、引数、戻り値など)を記述した型定義ファイル(Declaration File)が必要です。型定義ファイルは.d.ts
という拡張子を持ちます。
6.1 DefinitelyTypedと@types組織
多くの人気のあるJavaScriptライブラリの型定義ファイルは、DefinitelyTypedというオープンソースプロジェクトでコミュニティによって管理されています。これらの型定義ファイルは、npmの@types
スコープで公開されており、簡単にインストールして利用できます。
例えば、JavaScriptライブラリであるLodashの型定義ファイルが必要な場合、以下のコマンドでインストールできます。
bash
npm install --save-dev @types/lodash
これで、LodashライブラリをTypeScriptコード内で型安全に使用できるようになります。エディタでのコード補完も効くようになり、非常に便利です。
“`typescript
import * as _ from ‘lodash’;
const numbers = [1, 2, 3, 4, 5];
const shuffledNumbers = .shuffle(numbers); // .shuffle は number[] を引数に取り、number[] を返すことが型定義されている
console.log(shuffledNumbers);
// _.sortBy(numbers, ‘length’); // Error: Argument of type ‘”length”‘ is not assignable to parameter of type ‘((value: number) => unknown) | ((value: number) => number)[]’.
// number[] に対して ‘length’ でソートすることは Lodash の型定義で許されていないことがわかる
“`
ほとんどの主要なライブラリの型定義は@types
スコープで提供されていますが、もし利用したいライブラリの型定義が見つからない場合は、以下のいずれかの方法を検討します。
- そのライブラリ自体がTypeScriptで書かれているか、公式に型定義を提供しているか確認する。
- DefinitelyTypedに貢献して型定義を追加する。
- プロジェクト内で独自の型定義ファイルを作成する。
6.2 独自の型定義ファイル (.d.ts)
小規模な独自のJavaScriptコードや、型定義が提供されていないライブラリに対して、自分で型定義ファイルを作成することも可能です。
型定義ファイルの中では、変数の宣言にはdeclare let
またはdeclare const
、関数の宣言にはdeclare function
、クラスの宣言にはdeclare class
、モジュール全体にはdeclare module
など、declare
キーワードを使用します。declare
キーワードは「この変数/関数/クラス/モジュールは別の場所で定義されており、ここではその型だけを宣言します」という意味になります。
例:簡単なJavaScriptファイル my-js-lib.js
があるとします。
“`javascript
// my-js-lib.js
exports.add = function(a, b) {
return a + b;
};
exports.greet = function(name) {
console.log(“Hello, ” + name);
};
“`
このJavaScriptファイルに対する型定義ファイル my-js-lib.d.ts
を作成します。
typescript
// my-js-lib.d.ts
// declare module "my-js-lib" { // モジュールとして定義する場合
export function add(a: number, b: number): number;
export function greet(name: string): void;
// }
tsconfig.json
の"include"
オプションでこの.d.ts
ファイルが含まれるように設定されていれば、TypeScriptファイル内でmy-js-lib
モジュールをインポートして、型安全に利用できるようになります。
“`typescript
// main.ts
import { add, greet } from “./my-js-lib”; // または “my-js-lib” if declared as a module
const sum = add(10, 20); // add は number を引数に取り number を返す型として認識される
console.log(sum); // 30
greet(“Alice”); // greet は string を引数に取り void を返す型として認識される
// add(“hello”, 5); // Error: 型定義によってエラーが検出される
“`
型定義ファイルの作成は、既存のJavaScript資産を活用しながらTypeScriptの恩恵を受けるために非常に重要なスキルです。最初は難しく感じるかもしれませんが、既存の.d.ts
ファイルを参考にしながら少しずつ慣れていきましょう。
7. 主要なtsconfig.jsonオプションの詳細
tsconfig.json
ファイルには、TypeScriptコンパイラの振る舞いを制御するための多くのオプションがあります。ここでは、入門段階で特に理解しておきたい、または開発を進める上で重要になるいくつかのオプションを詳しく解説します。
前述のtsc --init
で生成されるファイルには多くのオプションがコメントアウトされています。使用したいオプションのコメントアウトを外して値を設定することで有効になります。
7.1 "target"
生成されるJavaScriptコードのECMAScriptバージョンを指定します。古いブラウザをサポートする必要がある場合は"es5"
や"es2015"
を選択しますが、モダンな環境では新しいバージョン("es2016"
, "es2017"
, "es2018"
, "es2019"
, "es2020"
, "es2021"
, "es2022"
, "esnext"
)を選択することで、TypeScriptコンパイラが新しいJavaScript構文をトランスパイルせずにそのまま出力するようになります。これはパフォーマンス面でも有利です。
例: "target": "es5"
は、アロー関数などを従来のfunction
構文に変換します。"target": "esnext"
は、可能な限り元の構文を維持します。
7.2 "module"
生成されるJavaScriptコードのモジュールシステムを指定します。
"commonjs"
: Node.jsで一般的に使用されるモジュール形式(require
/module.exports
)。"es6"
,"es2015"
,"esnext"
: ES Modules形式(import
/export
)。ブラウザや最新のNode.js環境で使用されます。"amd"
,"umd"
: RequireJSなどのAMDローダーや、CommonJS/AMD両方に対応するための形式。"system"
: SystemJSローダー向けの形式。"node16"
,"nodenext"
: Node.jsのES Modulesの仕様に合わせた形式。
プロジェクトのターゲット環境(Node.jsかブラウザか、どのバージョンのNode.jsか、バンドラーを使うかなど)に応じて適切に選択します。
7.3 "lib"
コンパイルに含まれるべき標準ライブラリの型定義ファイルを指定します。デフォルトではtarget
オプションに基づいて適切なライブラリが自動的に含まれます(例: "target": "es5"
なら["dom", "es5", "scripthost"]
、"target": "es2020"
なら["dom", "es2020"]
など)。
特定のAPI(例: DOM API, WebWorker API, 特定のECMAScriptバージョンで導入されたAPI)を使用する場合に、その型定義を含めるために明示的に指定することがあります。
例: "lib": ["dom", "es2020"]
は、DOM APIとES2020のAPIの型定義を含めます。
7.4 "strict"
TypeScriptの提供する最も重要なオプションの一つです。これをtrue
にすると、以下のすべての厳格チェックオプションが有効になります。
"noImplicitAny"
"strictNullChecks"
"strictFunctionTypes"
"strictBindCallApply"
"strictPropertyInitialization"
"noImplicitThis"
"useUnknownInCatchVariables"
可能な限り "strict": true
を有効にすることを強く推奨します。 これにより、TypeScriptの型チェックの恩恵を最大限に受けられ、多くの潜在的なバグを防ぐことができます。既存のJavaScriptプロジェクトにTypeScriptを導入する際は、段階的にこれらの厳格オプションを有効にしていくことも可能です。
7.5 "noImplicitAny"
型推論によってany
型と判断された場合にエラーを報告します。このオプションを有効にすることで、開発者が意識せずにany
型を使用してしまい、型チェックの恩恵を受けられなくなる状況を防ぎます。例えば、引数に型アノテーションがない関数の引数などがこれに該当します。
typescript
function processData(data) { // noImplicitAny: true の場合、ここで data が any 型と推論されてエラー
console.log(data.value);
}
これを避けるためには、明示的に型アノテーションを付ける必要があります。
typescript
function processData(data: any) { // any を許容する場合は明示的に書く
console.log(data.value);
}
// あるいはより具体的な型を付ける
function processData(data: { value: string }) {
console.log(data.value);
}
7.6 "strictNullChecks"
null
およびundefined
の扱いを厳格にします。このオプションがtrue
の場合、null
とundefined
は、それぞれの型(null
型、undefined
型)またはユニオン型 (string | null
) で明示的に許可されていない限り、他の型の変数に代入できなくなります。これにより、null
やundefined
に対する適切なチェックを強制し、実行時エラーを防ぎます。
“`typescript
let name: string;
name = “Alice”; // OK
// name = null; // Error: Type ‘null’ is not assignable to type ‘string’. (strictNullChecks: trueの場合)
let age: number | null = null; // OK
age = 30; // OK
“`
7.7 "outDir"
と "rootDir"
"outDir"
: コンパイルされたJavaScriptファイルが出力されるディレクトリを指定します。"rootDir"
: TypeScriptのソースファイルが存在するプロジェクトのルートディレクトリを指定します。この設定に基づいて、outDir
内に元のディレクトリ構造が再現されます。
例:
json
{
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
}
}
./src/app.ts
というファイルは ./dist/app.js
として出力されます。./src/utils/helper.ts
は ./dist/utils/helper.js
として出力されます。
7.8 "esModuleInterop"
CommonJSモジュール(Node.jsのrequire
)とES Modules(import
)の相互運用性を改善するためのオプションです。JavaScriptのエコシステムでは、CommonJSとES Modulesが混在している状況がよくあります。このオプションを有効にすると、TypeScriptコンパイラが生成するコードに互換性のためのヘルパーコードが追加され、import React from 'react'
のようなES Modules形式のインポート構文で、CommonJS形式でエクスポートされているモジュールを正しくインポートできるようになります。モダンな開発ではtrue
に設定することが推奨されます。
7.9 "skipLibCheck"
型定義ファイル(.d.ts
ファイル)の型チェックをスキップします。大規模なプロジェクトや、多数のライブラリを使用している場合に、型チェックにかかる時間を短縮する効果があります。ただし、これによりライブラリ側の型定義ファイルにエラーがあっても検出されなくなるため、注意が必要です。通常はtrue
に設定してビルド時間を短縮することが多いですが、ライブラリの型定義に問題がある場合は一時的にfalse
に戻してデバッグすることもあります。
7.10 その他の有用なオプション
"noUnusedLocals"
: 使用されていないローカル変数があった場合にエラーを報告します。"noUnusedParameters"
: 使用されていない関数のパラメータがあった場合にエラーを報告します。"sourceMap"
: ソースマップファイル(.map
ファイル)を生成します。これにより、生成されたJavaScriptコードではなく、元のTypeScriptコードを見ながらデバッグできるようになります。開発時にはほぼ必須のオプションです。"include"
/"exclude"
: コンパイル対象に含めるファイルやディレクトリ、除外するファイルやディレクトリをGlobパターンで指定します。デフォルトではtsconfig.json
があるディレクトリ以下のすべての.ts
,.tsx
,.d.ts
ファイル(ただしnode_modules
ディレクトリは除く)が対象になります。
tsconfig.json
のすべてのオプションについては、公式ドキュメント(https://www.typescriptlang.org/tsconfig)を参照してください。プロジェクトの要件に合わせてこれらのオプションを適切に設定することが、効率的かつ堅牢なTypeScript開発に不可欠です。
8. TypeScriptのエコシステム
TypeScriptは、JavaScriptの広範なエコシステムと緊密に連携しています。主要なフレームワークやライブラリはTypeScriptでの利用をサポートしており、開発ツールもTypeScriptに最適化されています。
8.1 主要なフレームワーク/ライブラリとTypeScript
- Angular: Angularは当初からTypeScriptで開発されており、TypeScriptを主要言語としています。CLIツールもTypeScriptプロジェクトの作成を標準でサポートしています。
- React: React自体はJavaScriptで書かれていますが、Create React AppやNext.jsなどのモダンな開発環境ではTypeScriptテンプレートが用意されており、TypeScriptでの開発が非常に一般的です。Reactの公式ドキュメントでもTypeScriptの使用が推奨されています。
- Vue.js: Vue 3はTypeScriptで書かれており、TypeScriptのサポートが大幅に強化されました。Vue CLIもTypeScriptプロジェクトテンプレートを提供しています。
- Node.js / Express: Node.js環境でサーバーサイドを開発する場合も、TypeScriptが広く使われています。Expressなどのフレームワークや、他の多くのNode.jsライブラリも
@types
で型定義が提供されています。
これらのフレームワークやライブラリでTypeScriptを使用することで、コンポーネントのPropsの型定義、状態管理の型定義、APIレスポンスの型定義などを行い、アプリケーション全体を通して型安全な開発が可能になります。
8.2 Linter (ESLint/TSLint), Formatter (Prettier) との連携
コードの品質を維持し、チームでのコーディングスタイルを統一するために、LinterやFormatterは必須のツールです。
- ESLint: JavaScriptおよびTypeScriptの最も一般的なLinterです。TypeScriptコードをチェックするためのプラグイン(
@typescript-eslint/eslint-plugin
など)が提供されており、TypeScriptの型情報を活用した高度なチェックも可能です。TSLintは非推奨となり、ESLintへの移行が推奨されています。 - Prettier: コードの自動整形ツールです。TypeScriptの構文にも対応しており、ESLintと連携して保存時に自動的にコードを整形するといった設定がよく行われます。
プロジェクトにESLintとPrettierを導入し、TypeScript向けの設定を行うことで、コードの一貫性を保ち、コードレビューの手間を減らすことができます。
8.3 ビルドツール (Webpack, Rollup, Parcel) とTypeScript
TypeScriptコードをJavaScriptに変換し、依存関係を解決して一つのファイルにまとめる(バンドルする)ために、Webpack, Rollup, Parcelなどのモジュールバンドラーがよく使われます。これらのツールはTypeScriptのコンパイル機能をプラグインやローダーとして組み込むことができます。
- Webpack:
ts-loader
やawesome-typescript-loader
などのローダーを使ってTypeScriptを処理します。設定の自由度が高いですが、複雑になることもあります。 - Rollup: ライブラリ開発に適したバンドラーで、
@rollup/plugin-typescript
などのプラグインでTypeScriptに対応します。 - Parcel: 設定不要を謳うモダンなバンドラーで、TypeScriptファイルを自動的に認識して処理します。
これらのバンドラーを使用することで、TypeScriptのコンパイルとモジュールバンドルを同時に行い、ブラウザ実行やNode.js実行に適したJavaScriptファイルを生成できます。
8.4 エディタサポート (VS Code)
TypeScriptはMicrosoftによって開発されているため、同じくMicrosoftが開発するVisual Studio Code (VS Code) との連携が非常に強力です。VS CodeはデフォルトでTypeScriptの強力な編集サポート(コード補完、型ヒント、エラー表示、リファクタリングなど)を提供しており、特別な設定なしに快適なTypeScript開発環境を利用できます。他のエディタ(Sublime Text, Atom, JetBrains IDEなど)もプラグインによってTypeScriptをサポートしています。
9. 実践的なTypeScriptの使い方
ここでは、既存のJavaScriptプロジェクトにTypeScriptを導入する際の手順や、型安全なコードを書くためのヒント、よくあるエラーへの対処法などを簡単に紹介します。
9.1 JavaScriptプロジェクトへの導入手順
既存のJavaScriptプロジェクトをTypeScriptに移行する場合、以下のステップで進めることが多いです。
- TypeScriptと型定義ファイルのインストール:
npm install --save-dev typescript @types/node
など、必要なパッケージをインストールします。 tsconfig.json
の作成:tsc --init
を実行し、プロジェクトの要件に合わせてtsconfig.json
を設定します。最初は緩やかな設定(例:strict
をfalse
にして、徐々に有効にしていく)で始めても良いでしょう。- ファイル拡張子の変更: 少量ずつ、または機能単位で、
.js
ファイルを.ts
または.tsx
(Reactを使用している場合) に変更します。 - 型エラーの修正: 拡張子を変更したファイルを開き、TypeScriptコンパイラやエディタが表示する型エラーを修正していきます。最初は
any
型を一時的に使用してエラーを解消し、徐々に具体的な型に置き換えていくアプローチも有効です。 - 型アノテーションの追加: 必要に応じて、関数の引数や複雑なオブジェクトなどに型アノテーションを追加し、型安全性を高めます。
- ビルド設定の更新: プロジェクトのビルドプロセス(Webpack, Parcelなど)にTypeScriptコンパイルを組み込みます。
- テストとデバッグ: 変更が正しく機能するか、実行時エラーが発生しないか確認します。
- 段階的な移行: 一度にすべてのファイルを移行するのではなく、少しずつ、または機能単位で移行を進めることで、リスクを管理しやすくなります。
9.2 型安全なコードを書くためのヒント
- 可能な限り厳格な設定を有効にする: 特に
"strict": true
は非常に重要です。 any
型を避ける:any
は型チェックを無効にするため、真に型の情報が得られない場合にのみ使用し、可能な限りunknown
や具体的な型を使用します。- 関数の引数には必ず型アノテーションを付ける: 関数のインターフェースを明確にするために最も基本的なことです。
- 関数の戻り値の型は、複雑な場合や意図を明確にしたい場合に明示する: 単純な戻り値は推論に任せても構いませんが、APIのコントラクトとなるような関数は戻り値も明示すると良いでしょう。
- オブジェクトや配列の構造を明確にする: インターフェースや型エイリアスを使って名前付きの型を定義することで、コードの可読性と再利用性が向上します。
- ユニオン型や判別可能なユニオンを活用する: 複数の型の可能性がある場合に、それらを正確に表現し、型の絞り込みを活用して安全に扱います。
- ジェネリクスを活用して再利用可能なコンポーネントを作成する: 型に依存しないロジックを書く場合にジェネリクスを使うと、様々な型に対して型安全なコードを提供できます。
- サードパーティライブラリには
@types
パッケージを利用する: 既存の高品質な型定義を利用することで、開発効率と信頼性が向上します。 - 早期にエラーを検出する: エディタのリアルタイムエラー表示を活用し、コーディング中にエラーを修正します。コンパイルエラーは実行時エラーよりも修正コストがはるかに低いことを常に意識します。
9.3 よくあるエラーとその対処法
Property 'x' does not exist on type 'Y'.
: 型Y
にプロパティx
が存在しません。これは、存在しないプロパティにアクセスしようとしたり、オブジェクトが期待する型を満たしていなかったりする場合に発生します。オブジェクトの型定義を確認するか、型の絞り込みを行ってプロパティへのアクセス前に型を保証する必要があります。Type 'A' is not assignable to type 'B'.
: 型A
の値が型B
の変数に代入できません。これは型が一致しない場合に発生します。代入しようとしている値の型と、代入先の変数の型定義を確認し、型を合わせる必要があります。ユニオン型を検討したり、意図的に型変換(Type Assertion,as Type
)を行うこともありますが、後者は慎重に使うべきです。Argument of type 'A' is not assignable to parameter of type 'B'.
: 関数やメソッドの呼び出しで、引数の型がパラメータの期待する型と一致しません。関数のシグネチャを確認し、正しい型の引数を渡す必要があります。Object is possibly 'null' or 'undefined'.
:"strictNullChecks": true
の場合に、null
またはundefined
である可能性があるオブジェクトのプロパティにアクセスしようとしたり、メソッドを呼び出そうとしたりした場合に発生します。アクセスする前にif (obj !== null && obj !== undefined)
のようなチェックを行うか、オプショナルチェイニング (obj?.prop
) を使用する必要があります。Cannot find name 'X'.
: 変数、関数、型などが定義されていません。定義が正しくインポートされているか、スコープ内にあるか確認してください。または、型定義ファイル (.d.ts
) が含まれていない可能性があります。
これらのエラーメッセージは、TypeScriptがあなたのコードの問題点を教えてくれているヒントです。メッセージをよく読み、どのコードのどの部分で型が一致しないのかを特定することで、問題の根本原因を見つけやすくなります。
10. まとめ:TypeScript学習の次のステップ
このガイドを通して、TypeScriptの基本的な考え方、環境構築、主要な型、そしてクラスやジェネリクスといった発展的な概念について学ぶことができました。TypeScriptは単なるJavaScriptの拡張ではなく、アプリケーション開発における堅牢性、保守性、開発効率を大きく向上させるための強力なツールです。
TypeScriptの旅はまだ始まったばかりです。さらに学習を進めるために、以下のステップを検討してください。
- 公式ドキュメントを読む: TypeScriptの公式ウェブサイト(https://www.typescriptlang.org/)には、より詳細な情報、ハンズオンガイド、言語仕様などが豊富にあります。
- 小さなプロジェクトをTypeScriptで書いてみる: 学んだことを実践するのが最も効果的です。簡単なToDoリストアプリケーションやデータ表示アプリなどをTypeScriptでゼロから作成してみてください。
- 既存のJavaScriptプロジェクトにTypeScriptを導入してみる: 小さな機能やモジュールから始めて、段階的にTypeScriptに移行する経験は非常に実践的です。
- 主要なフレームワーク(React, Vue, Angularなど)をTypeScriptで使ってみる: 実際の開発現場でTypeScriptがどのように活用されているかを学ぶ良い機会です。
- オープンソースプロジェクトのコードを読む: TypeScriptで書かれた有名ライブラリやフレームワークのコードを読むことで、より高度なTypeScriptの使い方や設計パターンを学べます。
- コミュニティに参加する: TypeScriptの質問ができるフォーラムやDiscordサーバーなどで他の開発者と交流し、疑問を解決したり、新しい知識を得たりしましょう。
- tsconfig.json のオプションをさらに深掘りする: プロジェクトの要件に応じて、様々なオプション(特にモジュール関連や厳格性オプション)を試してみてください。
TypeScriptは常に進化しています。新しいバージョンで追加される機能や改善点にも注目し続けることで、より効率的で質の高い開発を目指せるでしょう。
静的型付けの概念に慣れるまで少し時間がかかるかもしれませんが、一度そのメリットを実感すれば、もうJavaScriptだけの開発には戻れないと感じるかもしれません。TypeScriptは、あなたの開発スキルを次のレベルに引き上げ、より大規模で複雑なプロジェクトに自信を持って取り組むための強力な味方となるでしょう。
このガイドが、あなたのTypeScript学習の素晴らしい出発点となれば幸いです。頑張ってください!