第2章 TypeScriptの基本文法

前の章で、TypeScriptの概要と開発環境のセットアップ方法を学んだ。この章では、TypeScriptの基本文法を詳しく見ていこう。変数の型付け、関数、クラス、インターフェースなど、TypeScriptならではの機能を一つずつ解説していく。これらの機能を効果的に活用することで、型安全で保守性の高いコードを書けるようになるはずだ。

それでは、早速始めよう!

2.1 変数と型

TypeScriptの最大の特徴は、静的型付けにある。変数の型を宣言することで、コンパイル時に型エラーを検出できる。ここでは、基本的な型の種類と、型推論、型エイリアスについて学ぼう。

2.1.1 基本的な型(number, string, boolean, any, void)

TypeScriptには、以下のような基本的な型が用意されている。

  • number:数値型。整数と浮動小数点数の両方を表す。
  • string:文字列型。シングルクォート(‘)またはダブルクォート(“)で囲む。
  • boolean:真偽値型。trueまたはfalseのいずれかの値を取る。
  • any:任意の型。型チェックを回避したい場合に使用する。
  • void:何も返さない関数の戻り値の型。

変数の型は、変数名の後にコロン(:)を付けて宣言する。以下は、それぞれの型の変数宣言の例だ。

let count: number = 0;
let message: string = "Hello, TypeScript!";
let isActive: boolean = true;
let value: any = "文字列も数値も代入できる";

型を宣言しておくことで、コンパイラが型のミスマッチを検出してくれる。例えば、string型の変数に数値を代入しようとすると、エラーが発生する。

2.1.2 型推論

TypeScriptには型推論の機能があり、変数の初期化式から型を自動的に推論してくれる。明示的に型を宣言しなくても、代入された値から適切な型が割り当てられる。

let count = 0;  // numberと推論される
let message = "Hello, TypeScript!";  // stringと推論される

ただし、変数の宣言と初期化を別々に行う場合は、型を明示的に指定する必要がある。

let count;
count = 0;  // any型になってしまう

2.1.3 型エイリアス

型エイリアスを使うと、既存の型に別名を付けることができる。typeキーワードを使って定義する。

type UserID = string;
let id: UserID = "abc123";

これは、string型にUserIDという別名を付けている。型エイリアスは、複雑な型の表現を簡潔にするのに役立つ。

2.2 関数

関数は、特定の処理を実行し、値を返すためのコードブロックだ。TypeScriptでは、関数の引数と戻り値の型を指定することで、型安全な関数を定義できる。

2.2.1 関数の定義と呼び出し

関数の定義には、以下の構文を使う。

function 関数名(引数1: 型1, 引数2: 型2, ...): 戻り値の型 {
  // 関数の本体
}

例えば、2つの数値を受け取って和を返す関数は、以下のように定義できる。

function add(a: number, b: number): number {
  return a + b;
}

この関数を呼び出すには、関数名に引数を渡す。

let result = add(1, 2);  // 3

2.2.2 オプショナルパラメーター

関数の引数は、必須とオプショナルに分けることができる。オプショナルな引数は、引数名の後に疑問符(?)を付ける。

function greet(name: string, message?: string): void {
  if (message) {
    console.log(`${name}さん、${message}`);
  } else {
    console.log(`こんにちは、${name}さん`);
  }
}
 
greet("Alice");  // こんにちは、Aliceさん
greet("Bob", "お久しぶりです");  // Bobさん、お久しぶりです

2.2.3 デフォルトパラメーター

引数にデフォルト値を設定することもできる。デフォルト値は、引数リストの中で等号(=)を使って指定する。

function greet(name: string, message: string = "こんにちは"): void {
  console.log(`${name}さん、${message}`);
}
 
greet("Alice");  // Aliceさん、こんにちは
greet("Bob", "お久しぶりです");  // Bobさん、お久しぶりです

2.2.4 レストパラメーター

不特定多数の引数を受け取るには、レストパラメーターを使う。引数名の前に3つのドット(…)を付ける。

function sum(...numbers: number[]): number {
  return numbers.reduce((total, num) => total + num, 0);
}
 
console.log(sum(1, 2, 3, 4, 5));  // 15

レストパラメーターは、配列としてまとめられる。上の例では、numbersnumber[]型の配列となる。

2.2.5 アロー関数

アロー関数は、関数を簡潔に記述するための構文だ。=>を使って定義する。

const add = (a: number, b: number): number => {
  return a + b;
};

関数の本体が1つの式だけの場合、中括弧({})とreturn文を省略できる。

const add = (a: number, b: number): number => a + b;

アロー関数は、コールバック関数の記述などで頻繁に使われる。

2.3 クラス

クラスは、オブジェクト指向プログラミングの中心的な概念だ。TypeScriptでは、クラスベースのオブジェクト指向プログラミングがサポートされている。

2.3.1 クラスの定義

クラスは、classキーワードを使って定義する。

class Person {
  name: string;
  age: number;
 
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
 
  greet(): void {
    console.log(`こんにちは、私は${this.name}です。${this.age}歳です。`);
  }
}

このクラスは、Personというクラス名で定義されている。nameageはクラスのプロパティで、それぞれstring型とnumber型で宣言されている。

2.3.2 コンストラクタ

constructorは、クラスのインスタンスを生成するための特殊なメソッドだ。クラスのプロパティを初期化するために使う。

const alice = new Person("Alice", 20);

この例では、Personクラスのインスタンスを生成し、alice変数に代入している。コンストラクタの引数には、"Alice"20が渡されている。

2.3.3 メソッド

クラスには、メソッドを定義することができる。上の例では、greetメソッドが定義されている。このメソッドは、インスタンスのnameageを使ってメッセージを出力する。

alice.greet();  // こんにちは、私はAliceです。20歳です。

2.3.4 アクセス修飾子(public, private, protected)

クラスのプロパティとメソッドには、アクセス修飾子を付けることができる。

  • public:どこからでもアクセス可能(デフォルト)。
  • private:クラス内部からのみアクセス可能。
  • protected:クラス内部とサブクラスからアクセス可能。
class Person {
  private name: string;
  protected age: number;
 
  // ...
}

この例では、nameプロパティはprivateageプロパティはprotectedになっている。

2.3.5 継承

クラスは、他のクラスを継承することができる。extendsキーワードを使って継承元のクラスを指定する。

class Employee extends Person {
  private department: string;
 
  constructor(name: string, age: number, department: string) {
    super(name, age);
    this.department = department;
  }
 
  greet(): void {
    super.greet();
    console.log(`私は${this.department}部署に所属しています。`);
  }
}

この例では、EmployeeクラスがPersonクラスを継承している。superキーワードを使って、継承元のクラスのコンストラクタとメソッドを呼び出している。

2.4 インターフェース

インターフェースは、オブジェクトの型を定義するための構文だ。クラスの構造を定義したり、オブジェクトリテラルの型チェックに使ったりする。

2.4.1 インターフェースの定義

インターフェースは、interfaceキーワードを使って定義する。

interface Person {
  name: string;
  age: number;
  greet(): void;
}

この例では、Personインターフェースが定義されている。nameageのプロパティ、greetメソッドを持つオブジェクトの型を表している。

2.4.2 オプショナルプロパティ

インターフェースのプロパティは、オプショナルにすることができる。プロパティ名の後に疑問符(?)を付ける。

interface Person {
  name: string;
  age?: number;
}

この例では、ageプロパティがオプショナルになっている。

2.4.3 関数型

インターフェースを使って、関数の型を定義することもできる。

interface Greeter {
  (name: string): void;
}
 
const greeter: Greeter = (name: string) => {
  console.log(`Hello, ${name}!`);
};

この例では、Greeterインターフェースが関数型として定義されている。greeter変数は、このインターフェースを満たす関数でなければならない。

2.4.4 クラスへの実装

クラスは、インターフェースを実装することができる。implementsキーワードを使って、実装するインターフェースを指定する。

class Person implements Greeter {
  name: string;
 
  constructor(name: string) {
    this.name = name;
  }
 
  greet(): void {
    console.log(`Hello, my name is ${this.name}!`);
  }
}

この例では、PersonクラスがGreeterインターフェースを実装している。インターフェースで定義されているプロパティとメソッドを持っていなければならない。

2.5 ジェネリクス

ジェネリクスは、型を抽象化するための機能だ。型をパラメータ化することで、コードの再利用性を高められる。

2.5.1 ジェネリック関数

関数をジェネリック化するには、型パラメータをアングルブラケット(<>)で囲んで定義する。

function identity<T>(arg: T): T {
  return arg;
}

この例では、identity関数が型パラメータTを受け取る。引数の型と戻り値の型がTに抽象化されている。

2.5.2 ジェネリッククラス

クラスをジェネリック化することもできる。型パラメータをクラス名の後に定義する。

class Stack<T> {
  private items: T[] = [];
 
  push(item: T): void {
    this.items.push(item);
  }
 
  pop(): T | undefined {
    return this.items.pop();
  }
}

この例では、Stackクラスが型パラメータTを受け取る。内部の配列itemsの型がT[]に抽象化されている。

2.5.3 ジェネリック制約

型パラメータに制約を付けることで、特定の型でのみ動作するジェネリックを定義できる。extendsキーワードを使って、制約となる型を指定する。

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

この例では、getProperty関数が型パラメータTKを受け取る。KTのプロパティキーに制約されている。

2.6 モジュールシステム

TypeScriptには、モジュールシステムが用意されている。コードを複数のファイルに分割し、名前空間を管理することができる。

2.6.1 モジュールの作成

モジュールは、exportキーワードを使って作成する。変数、関数、クラス、インターフェースなどを外部に公開できる。

// myModule.ts
export const myValue = 42;
export function myFunction() {
  // ...
}

この例では、myValue変数とmyFunction関数がモジュールとして公開されている。

2.6.2 モジュールのインポートとエクスポート

他のモジュールから要素をインポートするには、importキーワードを使う。

// main.ts
import { myValue, myFunction } from './myModule';
 
console.log(myValue);
myFunction();

この例では、myModule.tsからmyValuemyFunctionをインポートしている。

また、export defaultを使って、モジュールのデフォルトエクスポートを指定することもできる。

// myModule.ts
export default function myDefaultFunction() {
  // ...
}

デフォルトエクスポートは、import文で直接指定できる。

// main.ts
import myDefaultFunction from './myModule';
 
myDefaultFunction();

モジュールシステムを活用することで、コードの管理がしやすくなり、名前の衝突を防ぐことができる。


以上が、TypeScriptの基本文法の解説だ。変数の型付け、関数、クラス、インターフェース、ジェネリクス、モジュールシステムについて学んだ。

TypeScriptの型システムを効果的に活用することで、コンパイル時のエラーチェックが強化され、バグの予防につながる。また、コードの可読性と保守性も向上する。

特に、大規模なプロジェクトでは、TypeScriptの恩恵が大きい。複数の開発者が関わる場合、型の情報がコードの意図を明確に伝えてくれるため、コミュニケーションコストを削減できる。

ただし、TypeScriptはJavaScriptのスーパーセットであるため、JavaScriptの知識も必要不可欠だ。TypeScriptの学習は、JavaScriptの理解を深めることにもつながるはずだ。

次の章では、TypeScriptを使ったWebアプリケーション開発について学んでいく。フロントエンドからバックエンドまで、TypeScriptを活用した実践的な開発手法を見ていこう。

TypeScriptの世界は奥深い。この言語の可能性を追求し、より高品質なコードを書けるようになることを目指して、学習を続けていってほしい。

それでは、次の章で会おう!頑張って学んでいこう!