読み込み中...

TypeScriptで静的型付けの全てを理解!初心者向け10のサンプルコード実例

TypeScriptの静的型付けを学ぶ初心者のためのイラスト TypeScript
この記事は約15分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

TypeScriptは、JavaScriptに静的型付けの能力を追加する人気のある言語です。

この特性により、開発者はコードのエラーを早期に発見し、より堅牢なアプリケーションを構築することが可能となります。

この記事では、静的型付けの基礎から応用、注意点、カスタマイズまで、10のサンプルコードを交えて徹底的に解説していきます。

TypeScriptの静的型付けのメリットを最大限に活用し、あなたのプログラミングスキルを次のレベルに引き上げるための情報が詰まっています。

このガイドを読み終わるころには、TypeScriptでの静的型付けに関する知識と技術を習得し、あなたのプログラミングライフに革命をもたらす準備が整っていることでしょう。

●TypeScriptと静的型付けの基本

近年のWeb開発において、TypeScriptはその人気を急上昇させています。

しかし、多くの初心者は「なぜTypeScriptが必要なのか?」と疑問に思うことでしょう。

ここでは、TypeScriptとは何か、そしてその魅力的な静的型付けの基本について深く掘り下げていきます。

○TypeScriptとは?

TypeScriptは、JavaScriptのスーパーセットとしてMicrosoftによって開発されたプログラミング言語です。

JavaScriptに型システムと強力なエディタの機能を追加することで、大規模なアプリケーションの開発を助け、エラーを早期に検出することができます。

JavaScriptのコードはそのままTypeScriptとしても動作するため、スムーズに移行することが可能です。

○静的型付けの魅力

「静的型付け」とは、変数や関数のパラメータに特定の型を宣言することを意味します。

この型宣言のおかげで、多くの一般的なエラーをコードが実行される前にキャッチすることができます。

具体的には、次のようなメリットが考えられます。

  1. 早期のエラー検出:コンパイル時に型の不整合や存在しないプロパティへのアクセスなどのエラーを発見できます。
  2. 読みやすいコード:型が明示的に宣言されているため、コードの読み手は変数や関数の期待される動作を明確に理解できます。
  3. 強力なエディタのサポート:Visual Studio CodeなどのエディタはTypeScriptの型情報を利用して、オートコンプリートやリファクタリングをサポートします。

このコードでは、文字列型と数値型を使って変数を宣言する基本的な方法を表しています。

この例では、変数nameに文字列を、ageに数値を割り当てています。

let name: string = "山田太郎";
let age: number = 30;

上記のように、TypeScriptでは変数の後ろにコロンをつけて型を明示的に宣言します。

このようにしてコードを実行すると、変数nameには”山田太郎”という文字列が、ageには30という数値が格納されます。

もし、ageに文字列を割り当てようとすると、TypeScriptのコンパイラはエラーを出力してくれます。

これにより、間違った型のデータが変数に割り当てられることを防ぐことができます。

●静的型付けの基礎

TypeScriptを使用する大きな利点の一つが、JavaScriptにはない静的型付けの機能です。

ここでは、TypeScriptでの静的型付けの基礎を徹底的に解説します。

○基本的な型の宣言方法

TypeScriptでは、変数や関数の引数、戻り値に型を宣言することができます。

これにより、コードの品質を向上させると同時に、エラーを事前にキャッチすることが可能になります。

□サンプルコード1:基本的な型の使い方

// 数値型を変数に宣言
let age: number = 25;

// 文字列型を変数に宣言
let name: string = "山田太郎";

// ブール型を変数に宣言
let isActive: boolean = true;

// 関数の引数と戻り値に型を宣言
function greet(person: string): string {
    return "こんにちは、" + person + "さん!";
}

このコードでは、変数agenameisActiveにそれぞれ数値型、文字列型、ブール型を指定しています。

また、関数greetでは、引数と戻り値に文字列型を指定しています。

この例では、変数と関数の型宣言の基本を学ぶことができます。

このコードを実行すると、関数greetに文字列を渡すことで、挨拶文が返されることが期待されます。

例えば、greet("山田太郎")というコードを追加すると、"こんにちは、山田太郎さん!"という結果が得られます。

○オブジェクトの型付け

TypeScriptでは、オブジェクトのプロパティにも型を宣言することができます。

これにより、オブジェクトの構造を明確にし、意図しないデータの割り当てを防ぐことができます。

□サンプルコード2:オブジェクトの型付け例

// オブジェクトの型をinterfaceで定義
interface Person {
    firstName: string;
    lastName: string;
    age: number;
    isStudent: boolean;
}

// 上記の型を持つオブジェクトを作成
let student: Person = {
    firstName: "太郎",
    lastName: "山田",
    age: 20,
    isStudent: true
};

// 関数でオブジェクトの型を使用
function getFullName(person: Person): string {
    return person.firstName + " " + person.lastName;
}

このコードでは、Personという名前のinterfaceを使って、オブジェクトの型を定義しています。

この例では、オブジェクトのプロパティの型を定義して、その型を持つオブジェクトを作成する方法を学ぶことができます。

このコードを実行すると、関数getFullNamePerson型のオブジェクトを渡すことで、フルネームが返されることが期待されます。

例えば、getFullName(student)というコードを追加すると、"太郎 山田"という結果が得られます。

●静的型付けの応用

静的型付けの基礎を把握したら、次に進むのは応用領域です。

TypeScriptでは、単純な型付けから高度な型操作までさまざまな機能が提供されています。

ここでは、それらの高度な機能を詳しく解説します。

○ジェネリクスの活用

ジェネリクスは、型の再利用性を高めるための強力な機能です。

関数やクラスなどの定義時に、具体的な型を指定せず、後から型を渡すことができるようにすることがジェネリクスの主な目的です。

□サンプルコード3:ジェネリクスを使用した関数

このコードでは、ジェネリクスを用いて配列の中の要素を取得する関数を表しています。

この例では、関数に渡す配列の型に関係なく、指定したインデックスの要素を取得しています。

function getElementFromArray<T>(arr: T[], index: number): T {
    // 配列から指定されたインデックスの要素を取得する
    return arr[index];
}

const numberArray = [1, 2, 3, 4, 5];
const stringArray = ['a', 'b', 'c', 'd'];

console.log(getElementFromArray(numberArray, 2)); // 3
console.log(getElementFromArray(stringArray, 1)); // 'b'

上記のコードを実行すると、getElementFromArray(numberArray, 2)3を、getElementFromArray(stringArray, 1)'b'をそれぞれ返します。

○ユニオン型とインターセクション型

ユニオン型とは、複数の型のいずれかを取りうることを表す型です。

一方、インターセクション型は、複数の型を合成して新しい型を作成する方法です。

□サンプルコード4:ユニオン型の例

このコードでは、ユニオン型を使って数字または文字列を取る変数を定義しています。

この例では、数字か文字列かのどちらかを取ることができる変数を宣言しています。

let value: number | string;

value = 10;
console.log(value); // 10

value = 'hello';
console.log(value); // 'hello'

上記のコードを実行すると、最初のconsole.log(value)10を、次のconsole.log(value)'hello'をそれぞれ出力します。

□サンプルコード5:インターセクション型の例

次に、インターセクション型の使用例を見てみましょう。

type FirstType = {
    id: number;
    name: string;
};

type SecondType = {
    age: number;
    city: string;
};

let combined: FirstType & SecondType;

combined = {
    id: 1,
    name: 'John',
    age: 25,
    city: 'Tokyo'
};

console.log(combined); // { id: 1, name: 'John', age: 25, city: 'Tokyo' }

上記のコードを実行すると、console.log(combined){ id: 1, name: 'John', age: 25, city: 'Tokyo' }を出力します。

○高度な型の操作

TypeScriptでは、より複雑な型操作を行うためのさまざまな機能が提供されています。

Mapped TypesやConditional Typesなど、これらの高度な型操作機能を使うことで、あらゆるシチュエーションでの型の柔軟な管理が可能になります。

□サンプルコード6:Mapped Typesの使用例

このコードでは、Mapped Typesを使用して、オブジェクトの各プロパティを読み取り専用に変換する方法を表しています。

この例では、既存の型のすべてのプロパティをreadonlyにして新しい型を作成しています。

type Original = {
    id: number;
    name: string;
};

type ReadOnlyOriginal = {
    readonly [K in keyof Original]: Original[K];
};

const obj: ReadOnlyOriginal = {
    id: 1,
    name: 'John'
};

// obj.id = 2; これはエラーとなります。

このコードでは、ReadOnlyOriginal型のobjのプロパティは読み取り専用となっており、変更を試みるとエラーが発生します。

□サンプルコード7:Conditional Typesの例

次に、Conditional Typesを利用した例を見てみましょう。

type IsString<T> = T extends string ? 'yes' : 'no';

type Result1 = IsString<'hello'>;  // 'yes'
type Result2 = IsString<number>;   // 'no'

上記のコードでは、IsString型は、指定された型が文字列であるかどうかを判定し、それに応じて'yes'または'no'の文字列リテラル型を返す型となっています。

●注意点と対処法

TypeScriptでの静的型付けを進めていく上で、様々な注意点やエラーが出ることがあります。

ここでは、そうした一般的な注意点と、それらの問題を効果的に回避するための対処法を紹介します。

○型の狭め方とエラー回避のコツ

TypeScriptでは、特定の型として変数を宣言した際に、その型以外の値を割り当てるとエラーが発生します。

このようなエラーは、型を狭く定義してしまうことが原因であることが多いです。

□サンプルコード8:型の狭め方の例

このコードでは、型を狭める方法として、文字列リテラル型を使用して変数を宣言し、その後でその型以外の値を代入しようとする例を表しています。

この例では、colorという変数に'red'または'blue'のみを許容するような型を定義しています。

type Color = 'red' | 'blue';

let color: Color;

color = 'red';   // これは許容される
// color = 'green';  // これはエラーが発生する

ここでcolor'green'を代入しようとすると、TypeScriptの型チェッカーはエラーを報告します。

Color型は'red''blue'のいずれかの値しか受け入れないため、このような代入は許容されません。

一般的な対処法としては、型を適切な範囲で定義することです。

具体的な値のセットを知っている場合や、特定の値のみを許容したい場合には、文字列リテラル型を利用するのが良いでしょう。

しかし、より広い範囲の値を許容したい場合には、より汎用的な型を使用することが推奨されます。

○非同期処理と型の取り扱い

非同期処理はJavaScriptの世界で非常に一般的ですが、TypeScriptでの型付けの際には特別な注意が必要です。

特に、Promiseを使用するときは、その戻り値の型を正確に指定することが重要です。

□サンプルコード9:Promiseと型付けの方法

このコードでは、非同期処理としてPromiseを返す関数を表しています。

この例では、数値を返す非同期関数fetchNumberを定義し、その戻り値の型をPromise<number>としています。

function fetchNumber(): Promise<number> {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(10);
        }, 1000);
    });
}

const result = fetchNumber();
console.log(typeof result);  // object

上記のコードを実行すると、console.log(typeof result)の出力はobjectとなります。

これは、fetchNumber関数がPromiseオブジェクトを返すためです。

非同期処理の結果を利用する場合、.thenasync/awaitを使用する必要があります。

●カスタマイズと拡張

TypeScriptを使用する一番の利点の一つは、既存の型をカスタマイズして、プロジェクトに合わせて拡張する能力です。

こうしたカスタマイズは、ソフトウェア開発の柔軟性を高め、より具体的なビジネス要件を満たすためのコードを書くのを容易にします。

○独自型の定義方法

TypeScriptは、プリミティブ型や複合型だけでなく、独自の型を定義するための豊富な機能を持っています。

これはinterfacetypeのキーワードを使用して行います。

これにより、具体的なビジネスロジックやデータ構造に合わせて、独自の型を設計できます。

このコードでは、新しい型を定義して、それを使用してオブジェクトを作成する方法を表しています。

この例では、Personという型を定義し、それに基づいてオブジェクトを作成しています。

// 独自の型「Person」を定義
interface Person {
    name: string;
    age: number;
    address?: string; // オプショナルなプロパティ
}

// 「Person」型を持つオブジェクトを作成
const takashi: Person = {
    name: "高橋",
    age: 28,
    address: "東京都新宿区"
};

// コメント:addressはオプショナルなので、以下も有効
const yoko: Person = {
    name: "陽子",
    age: 34
};

上記のサンプルでは、interfaceキーワードを使用してPerson型を定義しています。

この型にはname, age, addressの3つのプロパティがあり、addressはオプショナルとして定義されています。

そのため、yokoオブジェクトにはaddressプロパティが存在しないとしても、エラーは発生しません。

□サンプルコード10:独自の型宣言の作成

TypeScriptでは、より複雑な型の組み合わせも可能です。

例えば、異なる型の値を持つオブジェクトや、特定のプロパティを持つオブジェクトの配列などを定義できます。

下記のサンプルコードでは、商品情報を表すProduct型を定義し、その配列を持つInventory型を定義します。

// 商品情報を表す「Product」型を定義
interface Product {
    id: number;
    name: string;
    price: number;
}

// 「Product」の配列を持つ「Inventory」型を定義
interface Inventory {
    products: Product[];
}

const storeInventory: Inventory = {
    products: [
        { id: 1, name: "りんご", price: 100 },
        { id: 2, name: "みかん", price: 80 },
        { id: 3, name: "ぶどう", price: 300 }
    ]
};

このコードでは、Product型として商品情報を、Inventory型として在庫情報を表現しています。

このような型定義によって、TypeScriptはデータの構造や型の一貫性を保つ助けとなり、エラーの可能性を低減します。

上記のstoreInventoryオブジェクトは、3つの商品を持つ在庫情報として定義されています。

それぞれの商品はProduct型に従っており、全体の在庫情報はInventory型に従っています。

まとめ

今回の記事を通して、TypeScriptの静的型付けの基礎から応用、そして注意点やカスタマイズについての多岐にわたるトピックを詳しく解説してきました。

静的型付けは、ソフトウェア開発におけるエラーを予防し、コードの品質を高めるための非常に有効なツールとなっています。

この記事を通して、TypeScriptと静的型付けの真価をしっかりと理解し、実際の開発現場での活用の幅を広げることができたら嬉しいです。

初心者から経験者まで、TypeScriptの静的型付けを深く理解することで、より品質の高いコードを書き、開発効率やソフトウェアの安定性を向上させる手助けとなれば幸いです。