TypeScript言語の文法解説
基本文法から型システムの深い部分、データ構造・アルゴリズムの実装、コンパイルの仕組みまで、基本情報技術者試験に頻出するCS基礎トピックをTypeScriptで実装しながら解説します。
文法解説
1. コメント
1行コメントは //、複数行コメントは /* ... */ で書きます。JavaScriptと同じ書式です。
// これは1行コメント
let x: number = 1; // 行末にも書ける
/*
これは複数行コメント。
仕様のメモや関数の説明などに使う。
*/
function square(n: number): number {
return n * n;
}
2. 基本型
TypeScriptはJavaScriptのプリミティブ型に加え、配列・タプル・特殊な型を持ちます。anyはあらゆる型を許容し型チェックを無効化するため多用は禁物、unknownは「型不明だが安全に扱いたい値」に使う代替です。
let str: string = "文字列";
let num: number = 3.14;
let bool: boolean = true;
let arr: number[] = [1, 2, 3];
let arr2: Array<string> = ["a", "b"];
let tuple: [string, number] = ["age", 20]; // 要素数・型の順序が固定された配列
let anything: any = "何でも入る(型チェックが効かないので非推奨)";
let notSure: unknown = "使う前に型チェックが必要な値";
let nothing: void = undefined; // 戻り値がない関数の型に使う
function fail(msg: string): never { // 正常に戻らない関数(例外送出・無限ループ)
throw new Error(msg);
}
3. 型注釈と型推論
変数名の後ろに : 型 を書くのが型注釈です。初期値がある場合はTypeScriptが型を自動的に推論するため、注釈を省略しても型安全性は保たれます。
let age: number = 20; // 明示的な型注釈
let name = "太郎"; // string型と自動推論される
// name = 123; // エラー:string型にnumberは代入できない
function double(n: number) {
return n * 2; // 戻り値の型もnumberと推論される
}
4. ユニオン型・交差型・リテラル型
「AまたはB」を表すのがユニオン型(|)、「AかつB」を表すのが交差型(&)です。決まった値だけを許容するリテラル型と組み合わせるとよく使われます。
type Status = "success" | "error" | "loading"; // リテラル型のユニオン
let id: string | number;
id = "abc123";
id = 123;
interface Named { name: string; }
interface Aged { age: number; }
type Person = Named & Aged; // 交差型:両方のプロパティを持つ
const p: Person = { name: "花子", age: 22 };
5. インターフェースとtypeエイリアスの違い・構造的部分型
interfaceとtypeはどちらもオブジェクトの形を定義できますが、interfaceは同名宣言の自動マージ(宣言のマージ)やextendsによる拡張に向き、typeはユニオン型・交差型・タプルなどより柔軟な型表現に向きます。また、TypeScriptは名前ではなく構造(プロパティの形)が一致すれば互換とみなす「構造的部分型(Structural Typing)」を採用しています。
interface User {
name: string;
age: number;
email?: string; // ?はオプショナル(省略可能)
}
interface User { // 同名interfaceは自動的にマージされる
isAdmin?: boolean;
}
type Point = { x: number; y: number }; // typeはユニオン等にも使える
type ID = string | number;
// 構造的部分型:名前でなく形が同じなら代入可能
function printPoint(pt: { x: number; y: number }): void {
console.log(`(${pt.x}, ${pt.y})`);
}
const p: Point = { x: 1, y: 2 };
printPoint(p); // Point型はPointという名前を持たなくても形が一致すればOK
6. 関数の型定義(オーバーロード・デフォルト引数・可変長引数)
関数は引数と戻り値それぞれに型を指定します。同じ関数名で異なる引数パターンを定義する「オーバーロード」、値を省略したときの初期値を指定する「デフォルト引数」、任意個の引数をまとめて受け取る「可変長引数(レストパラメータ)」が使えます。
function add(a: number, b: number): number {
return a + b;
}
function greet(name: string, honorific: string = "さん"): string { // デフォルト引数
return `${name}${honorific}、こんにちは`;
}
function sumAll(...nums: number[]): number { // 可変長引数(レストパラメータ)
return nums.reduce((total, n) => total + n, 0);
}
sumAll(1, 2, 3, 4); // 10
// 関数オーバーロード:引数の型に応じて戻り値の型を変える
function toArray(x: number): number[];
function toArray(x: string): string[];
function toArray(x: number | string): number[] | string[] {
if (typeof x === "number") return [x];
return [x];
}
7. ジェネリクス
型を「引数」のように扱う仕組みです。呼び出し時に具体的な型が決まるため、複数の型に対応できる再利用可能な関数・クラスを型安全に作れます。
function first<T>(list: T[]): T {
return list[0];
}
const firstNum = first<number>([1, 2, 3]); // 1
const firstStr = first(["a", "b"]); // 型引数は省略しても推論される
class Box<T> {
constructor(private value: T) {}
getValue(): T {
return this.value;
}
}
const boxed = new Box<string>("宝物");
// 制約付きジェネリクス:Tはlengthプロパティを持つ型に限定
function logLength<T extends { length: number }>(value: T): number {
return value.length;
}
8. クラスとアクセス修飾子・継承・実装
public(デフォルト)は全公開、privateはクラス自身のみ、protectedはクラス自身と継承先からアクセス可能です。extendsでクラスを継承し、implementsでインターフェースの実装を強制できます。
interface Speakable {
speak(): string;
}
class Animal implements Speakable {
protected name: string; // 継承先からアクセス可能
private id: number = 0; // クラス内部のみ
constructor(name: string) {
this.name = name;
}
speak(): string {
return `${this.name}が鳴きました`;
}
}
class Dog extends Animal { // Animalを継承
speak(): string { // オーバーライド
return `${this.name}: ワン!`;
}
}
const d: Speakable = new Dog("ポチ");
console.log(d.speak());
9. Enum(列挙型)
関連する定数をひとまとめに定義できます。数値enumはコンパイル後に逆引き用オブジェクトが生成されるため、軽量にしたい場合は文字列enumやUnionリテラル型で代用することもあります。
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right, // 3
}
let dir: Direction = Direction.Up;
enum Color {
Red = "RED",
Blue = "BLUE",
} // 文字列enum:逆引きオブジェクトが生成されず軽量
10. Optional・Readonlyプロパティ
?を付けたプロパティは省略可能(オプショナル)、readonlyを付けたプロパティは初期化後に再代入できません。
interface Config {
readonly id: number; // 再代入不可
name: string;
timeout?: number; // 省略可能
}
const conf: Config = { id: 1, name: "server-a" };
// conf.id = 2; // エラー:readonlyプロパティは再代入できない
console.log(conf.timeout); // undefined(省略時)
11. 型ガード(型の絞り込み)
ユニオン型の変数をtypeofやinstanceof、独自の判定関数(ユーザー定義型ガード)で分岐すると、TypeScriptがブロック内での型を自動的に絞り込みます。
function printId(id: string | number): void {
if (typeof id === "string") {
console.log(id.toUpperCase()); // ここではstring型と確定
} else {
console.log(id.toFixed(2)); // ここではnumber型と確定
}
}
interface Cat { kind: "cat"; meow(): void; }
interface Bot { kind: "robot"; beep(): void; }
type Machine = Cat | Bot;
function activate(m: Machine): void {
if (m.kind === "cat") { // 判別可能なUnion(discriminated union)
m.meow();
} else {
m.beep();
}
}
12. 例外処理
try...catch...finallyで例外を捕捉します。catchで受け取る値の型は既定でunknownなので、使う前に型を確認する必要があります。
function parseAge(input: string): number {
const n = Number(input);
if (Number.isNaN(n)) {
throw new Error(`"${input}" は数値に変換できません`);
}
return n;
}
try {
const age = parseAge("abc");
console.log(age);
} catch (err) {
if (err instanceof Error) {
console.log(`エラー: ${err.message}`);
}
} finally {
console.log("処理を終了します");
}
13. データ構造の実装:ジェネリックなスタック
ジェネリクスを使うと、どんな型の要素でも安全に扱えるデータ構造を実装できます。ここではLIFO(後入れ先出し)で動作するスタックをクラスで実装します。push・popはいずれも計算量 O(1) です。
class Stack<T> {
private items: T[] = [];
push(item: T): void { // O(1)
this.items.push(item);
}
pop(): T | undefined { // O(1)
return this.items.pop();
}
peek(): T | undefined {
return this.items[this.items.length - 1];
}
isEmpty(): boolean {
return this.items.length === 0;
}
get size(): number {
return this.items.length;
}
}
const stack = new Stack<number>();
stack.push(1);
stack.push(2);
stack.push(3);
console.log(stack.pop()); // 3(最後に入れたものが最初に出る)
14. アルゴリズムと計算量(Big-O):二分探索とクイックソート
ソート済み配列から目的の値を探す二分探索は探索範囲を毎回半分にするため計算量 O(log n)、線形探索のO(n)より高速です。並べ替えのクイックソートは基準値(ピボット)で分割を繰り返し、平均計算量は O(n log n)、最悪の場合(既にソート済みなど偏った分割)はO(n²)になります。
// 二分探索:O(log n)
function binarySearch(arr: number[], target: number): number {
let low = 0;
let high = arr.length - 1;
while (low <= high) {
const mid = Math.floor((low + high) / 2);
if (arr[mid] === target) return mid;
if (arr[mid] < target) low = mid + 1;
else high = mid - 1;
}
return -1; // 見つからない
}
// クイックソート:平均O(n log n)、最悪O(n^2)
function quickSort(arr: number[]): number[] {
if (arr.length <= 1) return arr;
const [pivot, ...rest] = arr;
const left = rest.filter((n) => n < pivot);
const right = rest.filter((n) => n >= pivot);
return [...quickSort(left), pivot, ...quickSort(right)];
}
console.log(binarySearch([1, 3, 5, 7, 9], 7)); // 3
console.log(quickSort([5, 2, 8, 1, 9])); // [1, 2, 5, 8, 9]
15. strictモードとコンパイル(トランスパイル)の仕組み・tsconfig.json
TypeScriptのコードはブラウザやNode.jsで直接実行できず、tscコマンドなどでJavaScriptに変換(トランスパイル)してから実行します。この変換設定をまとめるのがtsconfig.jsonです。strictオプションを有効にすると、null/undefinedの扱いや暗黙のany禁止など、多くの厳格な型チェックが一括で有効になります。
{
"compilerOptions": {
"target": "ES2020", // 出力するJavaScriptのバージョン
"module": "ESNext", // 出力するモジュール形式
"strict": true, // 厳格な型チェックを一括有効化
"outDir": "./dist", // コンパイル後のJS出力先
"rootDir": "./src", // TypeScriptソースの場所
"skipLibCheck": true // ライブラリの型定義チェックを省略しビルド高速化
},
"include": ["src/**/*"]
}
16. モジュール(import / export)
1つのファイルを1つのモジュールとして扱い、exportで公開した機能を他ファイルからimportして利用します。名前付きエクスポートとデフォルトエクスポートの2種類があります。
// math.ts
export function add(a: number, b: number): number {
return a + b;
}
export const PI = 3.14159;
export default class Calculator {
square(n: number): number {
return n * n;
}
}
// main.ts
import Calculator, { add, PI } from "./math";
console.log(add(1, 2));
console.log(PI);
console.log(new Calculator().square(4));
処理速度改善のポイント
TypeScriptの型情報はコンパイル時にのみ存在し、コンパイル後は通常のJavaScriptとして実行されるため実行速度そのものは変わりません。しかし書き方やビルド設定次第で、開発効率・出力コードのサイズ・実行時の安全性に差が出ます。
- strictモードを有効にする:コンパイル時に型の不整合を検出でき、実行時に本来不要な防御的nullチェックを減らせます。
- anyの多用を避けunknownや具体的な型を使う:anyは型チェックを無効化し、バグ検出の機会を失って後工程での修正コストが増えます。
- 型ガードで早期に型を絞り込む:ユニオン型を早い段階で絞り込むことで、無駄な分岐やas による強制キャストを減らせます。
- 数値enumよりUnionリテラル型やas constを使う:数値enumはコンパイル後に逆引き用オブジェクトが生成され、余分なコードサイズと処理が発生します。
- tsconfigのtargetを実行環境に合わせる:古いtargetを指定すると不要なポリフィルやトランスパイルコードが増え、読み込み・実行速度が低下します。
- 大規模プロジェクトではincremental・skipLibCheckを活用:差分ビルドやライブラリ型チェック省略でコンパイル時間を短縮し開発効率を上げられます。
- 複雑すぎる条件型・再帰的な型を避ける:高度な型計算を多用しすぎるとコンパイル時間(型チェックのコスト)が増大します。
- アルゴリズムの計算量そのものを見直す:型を整えても計算量がO(n²)のままでは根本的な速度は改善しません。適切なデータ構造・アルゴリズムの選択が最重要です。