読み込み中...

【TypeScript】アノテーション完全解説!脱初心者のための10選サンプルコード

初心者向けのTypeScriptアノテーションのサンプルコード集 TypeScript
この記事は約21分で読めます。

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

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

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

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

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

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

はじめに

近年、多くの開発者がJavaScriptに代わるものとしてTypeScriptに注目しています。

TypeScriptは、JavaScriptのスーパーセットとして位置づけられ、静的型付けの利点を持ちながらも、JavaScriptの動的な特性を損なわない言語として知られています。

この記事では、TypeScriptの魅力的な機能の1つである「アノテーション」に焦点を当て、その基本的な使い方から応用例まで、10のサンプルコードを交えて徹底的に解説します。

初心者の方でもアノテーションの魅力を実感し、日々のコーディングに役立てられるような情報を提供することを目指しています。

この記事を通じて、アノテーションの使い方の理解を深めるとともに、実際の開発現場での活用方法も学んでいただければ幸いです。

●TypeScriptとは?

TypeScriptは、Microsoftが開発したオープンソースのプログラミング言語です。

JavaScriptのスーパーセットとして、JavaScriptのコードはそのままTypeScriptとしても動作します。

しかし、TypeScriptの真の魅力は静的型付けとその他の強力な機能にあります。

○基本的な特徴

JavaScriptの動的型付けに対して、TypeScriptは静的型付けを採用しています。

これにより、コードのエラーをコンパイル時に検出することができ、バグの発見やデバッグが容易になります。

また、型を持つことで、IDEの補完機能が向上し、開発効率も大きく向上します。

加えて、TypeScriptはクラスベースのオブジェクト指向、ジェネリクス、インターフェースなど、JavaScriptにはない多くの機能を持っています。

これにより、大規模なアプリケーションの開発や、複数人での開発がしやすくなっています。

●アノテーションの基本

アノテーションは、変数や関数、クラスなどに型情報を付与するための機能です。

この型情報はコンパイル時にチェックされ、指定された型と異なる値が代入されるとエラーが発生します。

○アノテーションの役割

アノテーションの主な役割は、変数や関数の引数・戻り値などの型を明示的に指定することです。

これにより、コードの読みやすさが向上し、予期しないエラーを防ぐことができます。

例えば、関数の引数に期待する型を明示的に指定することで、関数の使用方法が明確になり、その関数を使用する際のミスを減少させることができます。

●アノテーションの使い方

○サンプルコード1:基本的な型アノテーション

このコードでは基本的な型アノテーションを使って変数の型を指定するコードを表しています。

この例では変数を数値型や文字列型として定義しています。

let num: number = 10;
let str: string = "こんにちは";

上記のコードにおいて、変数numは数値型として、strは文字列型として定義されています。

このように変数の後ろに:をつけて型を指定することで、アノテーションを行うことができます。

コードを実行すると、指定した型通りの変数が生成されます。

ここでは特にエラーなどは発生せず、変数numstrがそれぞれ数値と文字列として定義されます。

○サンプルコード2:オプションのパラメータ

このコードでは関数の引数にオプションのパラメータを持つコードを表しています。

この例では関数greetの引数nameがオプションであることを表しています。

function greet(name?: string): string {
  if (name) {
    return "こんにちは、" + name + "さん";
  } else {
    return "名前を教えてください";
  }
}

この関数を呼び出す際、引数nameを省略してもエラーになりません。

引数が指定されていればその名前を使って挨拶し、省略されていれば異なるメッセージを返します。

関数を実行すると、引数に名前を指定した場合はその名前を使った挨拶メッセージが、指定しなかった場合は「名前を教えてください」というメッセージが返されます。

○サンプルコード3:関数の型アノテーション

このコードでは関数の引数と戻り値に型アノテーションをつけるコードを表しています。

この例では関数addで二つの数値を受け取り、その合計値を返すことを表しています。

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

この関数では、引数abの型を数値型として指定し、戻り値も数値型としています。

このため、関数を実行する際に非数値を引数として渡すとエラーとなります。

関数を実行すると、引数に指定した二つの数値の合計値が返されます。

例えば、add(5, 3)とすると、結果として8が返されます。

●アノテーションの応用例

TypeScriptでのアノテーションは、基本的な型定義からさらに応用的な型の組み合わせまで、多岐にわたります。

ここでは、アノテーションの応用的な使い方をいくつかのサンプルコードとともに紹介します。

○サンプルコード4:インターフェースの使用

このコードでは、TypeScriptのインターフェースを使ってオブジェクトの型を定義しています。

インターフェースを使用することで、オブジェクトの構造を明確に表現することができます。

// ユーザー情報を表すインターフェースの定義
interface User {
  id: number;
  name: string;
  email?: string;  // emailはオプショナル属性として定義
}

// Userインターフェースに従ったオブジェクトの作成
const user1: User = {
  id: 1,
  name: '田中太郎'
};

この例では、Userというインターフェースを定義し、その後でこのインターフェースに基づいてオブジェクトuser1を作成しています。

また、emailはオプショナル属性として定義されており、必須ではありません。

このコードを利用すると、user1オブジェクトはUserインターフェースの構造に基づいていることが保証されます。

そのため、安全にオブジェクトを扱うことができます。

○サンプルコード5:ジェネリクスの活用

このコードでは、TypeScriptのジェネリクスを活用して関数やクラスを汎用的に使用する方法を表しています。

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

// ジェネリクスを使用した関数の定義
function getArrayItem<T>(array: T[], index: number): T {
  return array[index];
}

// 文字列の配列を扱う例
const stringArray = ['a', 'b', 'c'];
const item1 = getArrayItem<string>(stringArray, 1);  // b

// 数字の配列を扱う例
const numberArray = [10, 20, 30];
const item2 = getArrayItem<number>(numberArray, 2);  // 30

この例では、ジェネリクスを使った関数getArrayItemを定義しています。

この関数は、任意の型の配列とインデックスを受け取り、指定されたインデックスの要素を返します。

文字列の配列を例として取り上げると、getArrayItem<string>(stringArray, 1)という関数呼び出しの結果は、bという文字が得られます。

同様に、数字の配列の場合は30という数字が得られることが確認できます。

○サンプルコード6:型ガードとアノテーション

TypeScriptのアノテーションの中でも、特に実践的な部分として「型ガード」が挙げられます。

型ガードは、ある変数が特定の型であるかを確認し、その結果に基づいて処理を分岐する機能です。

これにより、コード内で変数の型を確実に扱うことができ、安全性が向上します。

このコードでは、型ガードを使用して、動物の種類を判別し、それに応じて特定の動作を出力する例を表しています。

具体的には、動物が「魚」か「鳥」かを判別し、それぞれの動物が持つ特有の動作(泳ぐ、飛ぶ)を出力します。

interface 魚 {
  泳ぐ: () => void;
}

interface 鳥 {
  飛ぶ: () => void;
}

// 型ガードの関数定義
function is魚(動物: 魚 | 鳥): 動物 is 魚 {
  return (動物 as 魚).泳ぐ !== undefined;
}

// 使用例
const マグロ: 魚 = {
  泳ぐ: () => console.log("マグロは速く泳ぎます。")
}

const スズメ: 鳥 = {
  飛ぶ: () => console.log("スズメは高く飛びます。")
}

function 動物の動き(動物: 魚 | 鳥) {
  if (is魚(動物)) {
    動物.泳ぐ();
  } else {
    動物.飛ぶ();
  }
}

動物の動き(マグロ);  // マグロは速く泳ぎます。
動物の動き(スズメ);  // スズメは高く飛びます。

この例のポイントは、is魚という型ガード関数を使用して、引数に渡された動物型か型かを確認しています。

この関数が真を返す場合、その後のコード内では動物型であると確定され、安全に泳ぐメソッドを呼び出すことができます。

実際に上記のコードを実行すると、「マグロは速く泳ぎます。」と「スズメは高く飛びます。」という出力が得られることが期待されます。

○サンプルコード7:複合型の活用

TypeScriptにおいて、データの形や構造をより柔軟に記述するために、複合型という概念を利用します。

複合型は、異なる型を組み合わせて新しい型を作ることができます。

これによって、より詳細な型アノテーションを行い、コードの安全性を向上させることが可能になります。

このコードでは、複合型の基本的な使い方を表しています。

この例では、union型とintersection型を用いて、複数の型を組み合わせて新しい型を作成しています。

// Union型: 複数の型のいずれかが許容される
type StringOrNumber = string | number;

let value: StringOrNumber;
value = "Hello TypeScript"; // 文字列を代入
value = 123; // 数値を代入

// Intersection型: 複数の型の属性やメソッドを組み合わせる
type User = {
    id: number;
    name: string;
};

type Product = {
    id: number;
    price: number;
};

type PurchaseHistory = User & Product;

const purchase: PurchaseHistory = {
    id: 1,
    name: "Taro",
    price: 2000
};

このサンプルでは、まずunion型を使用して、StringOrNumberという新しい型を定義しています。

これにより、変数valueには、文字列または数値を代入することができます。

次にintersection型を使用して、UserProductという二つの型を組み合わせて、PurchaseHistoryという新しい型を作成しています。

この新しい型は、UserProductの両方の属性を持つオブジェクトを表します。

複合型を使用することで、TypeScriptはさらに柔軟な型システムを持つことができます。

特に、既存の型を組み合わせて新しい型を定義する際に非常に有用です。

実際に上記のコードを実行すると、エラーなくコンパイルが完了し、変数valueと変数purchaseに正しく値が代入されることが確認できます。

これにより、複合型が正しく動作していることがわかります。

しかし、複合型を使用する際の注意点として、不要な型の組み合わせを避けることが挙げられます。

例えば、string & numberのような組み合わせは、実際には使用されることのない不適切な型を作成することになるため、注意が必要です。

応用例として、実際のアプリケーション開発でのデータ構造のモデリングに複合型を活用することが考えられます。

例えば、ユーザー情報と商品情報を組み合わせて購入履歴を表現する場合などには、上述のようなintersection型を用いて効果的に型を定義することができます。

カスタマイズ例として、複数の型を組み合わせて新しい型を作成する際に、オプショナルな属性やデフォルトの値を持つ属性を追加することも考えられます。

これにより、より柔軟なデータ構造をモデリングすることができます。

type EnhancedUser = User & {
    email?: string; // オプショナルな属性
    age: number = 25; // デフォルトの値を持つ属性
};

このように、複合型を上手く活用することで、TypeScriptの型システムの強力さを最大限に引き出すことができます。

初心者の方も、この機能を使って、より高品質なコードを書くことができるでしょう。

○サンプルコード8:型エイリアスの利用

TypeScriptでは、特定の型を独自の名前で再利用することが可能です。

これを「型エイリアス」といい、独自の型を作成する際や複雑な型を簡単な名前で扱う場面で非常に役立ちます。

このコードでは型エイリアスを使って独自の型を定義する方法を表しています。

この例では、ユーザー情報を表す型をエイリアスとして作成し、それを使用しています。

// 型エイリアスを利用してUser型を定義
type User = {
  id: number;
  name: string;
  email: string;
};

// User型の変数を宣言
let user: User = {
  id: 1,
  name: '田中太郎',
  email: 'tanaka@example.com'
};

console.log(user.name);  // 田中太郎

このサンプルコードでは、Userという名前で新しい型を定義しています。

このUser型はオブジェクト型で、id, name, emailという3つのフィールドを持ちます。

そして、User型の変数userを宣言し、その変数にユーザー情報を代入しています。

最後に、ユーザー名をコンソールに表示しています。

このコードを実行すると、コンソールに「田中太郎」と表示されます。

型エイリアスは、特定の型を再利用したい場合や、複数の場所で同じ型を使用する場面で特に有効です。

しかし、型エイリアスを利用する際には注意も必要です。

型エイリアスは再代入ができないため、一度定義したエイリアス名を変更したり、同じ名前で別の型を定義することはできません。

次に、型エイリアスの応用例を見ていきましょう。

型エイリアスは、複数の型を組み合わせたユニオン型と一緒に使用することもできます。

下記のコードは、文字列か数値のどちらかを受け取る型を型エイリアスで定義しています。

// 文字列または数値のユニオン型を定義
type StringOrNumber = string | number;

// StringOrNumber型の変数を宣言
let value: StringOrNumber;

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

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

この例ではStringOrNumberという型エイリアスを定義し、文字列または数値を代入することができる変数valueを宣言しています。

変数valueには文字列もしくは数値のどちらかを代入できるため、コードを実行すると「Hello」と「123」という2つの出力が得られます。

型エイリアスを利用することで、コードの可読性を向上させ、型の再利用を簡単にすることができます。

特に、複雑な型や頻繁に使用する型に名前を付けて再利用することで、エラーの発生を防ぎやすくなるので、ぜひ積極的に活用してみてください。

○サンプルコード9:アノテーションを用いたデコレータ

TypeScriptでは、デコレータという非常に強力な機能が提供されています。

デコレータは、クラスやメソッド、プロパティ、パラメータなどにメタデータを追加したり、それらの定義を変更したりするための特殊な種類の宣言です。

ここでは、アノテーションを使用してデコレータを作成する方法を探ります。

デコレータは、”@”記号とそれに続く式から成り立っています。

この式は、デコレートされる宣言に対して呼び出される関数を評価するものです。

まず、基本的なデコレータのサンプルコードを見てみましょう。

// ログデコレータの作成
function log(target: any, propertyName: string): void {
  // プロパティに関する情報をログに出力
  console.log(`Log: ${target} が ${propertyName} という名前のプロパティを持っています。`);
}

class MyClass {
  // デコレータの使用
  @log
  public name: string;

  constructor(name: string) {
    this.name = name;
  }
}

const example = new MyClass("John");

このコードでは、logという名前のデコレータを作成しています。

このデコレータはクラスのプロパティに関する情報をログに出力します。

そして、MyClassというクラスのnameプロパティにこのデコレータを適用しています。

この例で新しくMyClassのインスタンスを生成すると、コンソールにはLog: [object Object] が name という名前のプロパティを持っています。

というメッセージが表示されます。

これは、logデコレータがプロパティが定義されたタイミングで実行され、関連する情報をログに出力した結果です。

デコレータの強力な点は、様々な箇所に適用できることや、デコレータの内部で宣言の振る舞いを変更できることです。

例えば、クラスのメソッドやアクセス修飾子などもデコレータを通じて操作が可能です。

注意すべき点として、デコレータは実験的な機能としてTypeScriptに導入されているため、使用する際にはexperimentalDecoratorsオプションをtsconfig.json内で有効にする必要があります。

○サンプルコード10:条件型とアノテーション

TypeScriptの条件型は非常に強力な機能であり、多様なシチュエーションでコードの型安全性を高めるのに役立ちます。

ここでは、条件型とアノテーションの組み合わせによる実用的なコード例を表しています。

この例では、条件型を使用して特定の型を返す関数を作成しています。

// Tがstringなら'文字列'、それ以外なら'非文字列'を返す条件型を定義
type IsString<T> = T extends string ? '文字列' : '非文字列';

// 使用例
type A = IsString<string>;  // Aは'文字列'になります。
type B = IsString<number>;  // Bは'非文字列'になります。

コメントで示されている通り、このコードではIsStringという条件型を使って、与えられた型がstringであるかどうかを判断しています。

もしstring型であれば、'文字列'というリテラル型を返し、それ以外の型であれば'非文字列'というリテラル型を返します。

この条件型を実際に使用すると、type A'文字列'という型となり、type B'非文字列'という型となります。

このように条件型を使用することで、与えられた型に基づいて異なる型を返すことができます。

このような条件型の利用は、大規模なプロジェクトやライブラリの開発時に特定の型が期待される状況を柔軟に対応するために非常に有用です。

さらに、条件型をより複雑な形で活用することも可能です。

例えば、次のような条件型も考えられます。

// Tが配列型なら、その要素の型を返す条件型を定義
type ElementType<T> = T extends (infer U)[] ? U : never;

// 使用例
type C = ElementType<string[]>;   // Cはstringになります。
type D = ElementType<number[]>;   // Dはnumberになります。

この例では、ElementTypeという条件型を使って、与えられた型が配列型であるかどうかを判断し、配列の要素の型を返しています。

この条件型を利用すると、type Cstring型となり、type Dnumber型となります。

TypeScriptの条件型は、これらのシンプルな例だけでなく、より高度な型操作もサポートしています。

プロジェクトのニーズに応じて、条件型を駆使してコードの品質を向上させることができます。

●注意点と対処法

TypeScriptのアノテーションを使用する際、初心者は特にいくつかの一般的な問題や誤解に直面することがあります。

ここでは、それらの注意点とそれを回避または解決するための対処法を紹介していきます。

○型の互換性

このコードでは、TypeScriptの型互換性の問題を取り上げています。

この例では、異なる型の間の不整合を防ぐ方法を表しています。

let item1: string = "Hello";
// item1 = 123; // この行はエラーとなる

コメントの行のように、文字列型の変数に数値を代入しようとすると、TypeScriptはエラーを発生させます。

型が一致しない代入を回避することで、ランタイムエラーのリスクを減少させることができます。

ここでの対処法は、明確に型を指定することです。

型アノテーションを活用することで、予期せぬエラーを回避できます。

○型推論と明示的な型指定

このコードでは、型推論と明示的な型指定のバランスをとる方法を表しています。

この例では、変数の初期化時にTypeScriptが型を推論する様子を見ています。

let item2 = "World"; // TypeScriptはitem2をstring型と推論
// item2 = 456; // この行はエラーとなる

型推論を利用することで、冗長な型アノテーションの記述を減少させることができます。

しかし、意図しない型になる場合もあるため、必要に応じて明示的な型指定を行うことが重要です。

○アノテーションとAny型

このコードでは、any型の使用とそのリスクについて表しています。

この例では、any型を使用することで型チェックがスキップされることを表しています。

let item3: any = "TypeScript";
item3 = 789; // これはエラーにならない

any型を使用すると、型チェックの利点を失う可能性があります。

必要最低限の場面でのみ使用し、他の場合は具体的な型を指定することが推奨されます。

●カスタマイズ方法

TypeScriptのアノテーションは、柔軟性と拡張性の両方を持っています。

そのため、ユーザーの要件や状況に応じて、カスタマイズすることができます。

ここでは、アノテーションをカスタマイズするための基本的な方法とサンプルコードをいくつか紹介します。

○カスタム型アノテーションの作成

TypeScriptでは、アノテーションを使用して独自の型を定義することができます。

これにより、コードの可読性と再利用性を向上させることができます。

下記のサンプルコードは、ユーザーの名前と年齢を持つPerson型を定義する例です。

type Person = {
    name: string;
    age: number;
};

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

この例では、名前は文字列型、年齢は数値型としてアノテーションされています。

もし、このPerson型を使用してオブジェクトを作成する場合、次のようになります。

const user: Person = {
    name: "山田太郎",
    age: 25
};

このコードを実行すると、userオブジェクトはPerson型として認識され、その属性としてnameageが存在することが確認できます。

○アノテーションの拡張

既存のアノテーションを拡張して、新しい属性やメソッドを追加することも可能です。

これにより、より複雑なデータ構造や機能を持つ型を作成することができます。

下記のサンプルコードは、先ほどのPerson型を拡張して、新たにaddress属性を追加する例です。

type DetailedPerson = Person & {
    address: string;
};

const detailedUser: DetailedPerson = {
    name: "山田太郎",
    age: 25,
    address: "東京都新宿区"
};

この例では、DetailedPerson型はPerson型を継承しており、新たにaddress属性を持っています。

detailedUserオブジェクトは、この新しい型を使用して作成されています。

まとめ

TypeScriptのアノテーションは、コードの品質や保守性を向上させるための強力なツールです。

この記事では、TypeScriptアノテーションの基本から応用まで、10のサンプルコードを通じて詳しく解説しました。

初心者の方も、これらのサンプルコードを参考に、アノテーションの使い方やその魅力を実感することができるでしょう。

この記事での知識を元に、アノテーションを積極的に取り入れ、品質の高いコードの作成を目指してください。