🐙

TypeScriptの基本概念だけを学習してみた

2024/06/16に公開

はじめに

TypeScript は JavaScript に型システムを追加した言語で、静的型付けや強力なツールサポートを提供します。この記事では、TypeScript の基本的な概念について学び、そのメリットを探ります。

型システム

TypeScript の型システムは、コンパイル時にエラーを検出し、コードの品質を向上させます。

型アノテーション

let message: string = "Hello, TypeScript";
console.log(message);

このコードでは、message 変数に対して string 型を指定しています。

型システムのメリット

  1. 早期エラー検出: 型の不一致などのエラーをコンパイル時に検出できるため、バグを早期に発見できます。

  2. 自己文書化コード: 型アノテーションを追加することで、コードの意図が明確になり、他の開発者にとって理解しやすくなります。

  3. IDE サポート: 型情報に基づいて、IDE がより良い補完やリファクタリング支援を提供します。

インターフェース

TypeScript では、インターフェースを使用してオブジェクトの形状を定義できます。これにより、構造的な型付けが可能になります。

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

function greet(person: Person) {
  return `Hello, ${person.name}`;
}

let user = { name: "John", age: 30 };
console.log(greet(user));

このコードでは、Person インターフェースを定義し、その形状に従うオブジェクトを引数に取る greet 関数を作成しています。

インターフェースのメリット

  1. コードの一貫性: インターフェースを使用することで、オブジェクトの構造を一貫して管理できます。

  2. コードの再利用性: インターフェースを利用することで、共通の構造を持つ異なるオブジェクトを扱う関数やクラスを再利用できます。

  3. 明確な契約: インターフェースは、コード間の明確な契約を定義し、誤用を防ぎます。

型エイリアス

型エイリアスを使用すると、複雑な型定義を簡潔に表現できます。

type StringOrNumber = string | number;

function print(value: StringOrNumber) {
  console.log(value);
}

print("Hello");
print(42);

このコードでは、StringOrNumber 型エイリアスを定義し、string または number の値を受け取る関数 print を作成しています。

型エイリアスのメリット

  1. 可読性向上: 複雑な型定義を簡潔に表現することで、コードの可読性が向上します。

  2. 再利用性: 型エイリアスを使用することで、同じ型定義を複数の場所で再利用できます。

ジェネリクス

TypeScript では、ジェネリクスを使用して汎用的な関数やクラスを作成できます。ジェネリクスにより、異なる型を扱うコードの再利用が容易になります。

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

console.log(identity<string>("Hello"));
console.log(identity<number>(42));

このコードでは、ジェネリック関数 identity を定義し、任意の型 T を受け取ることができます。

ジェネリクスのメリット

  1. コードの再利用性: ジェネリクスを使用することで、同じロジックを異なる型に対して再利用できます。

  2. 型安全性: ジェネリクスを使用することで、型安全なコードを記述することができ、ランタイムエラーを防ぐことができます。

  3. コードの簡潔性: ジェネリクスを使用することで、同じ処理を行うために複数の関数やクラスを定義する必要がなくなり、コードが簡潔になります。

非同期処理と Promise

TypeScript は、非同期処理を扱うために Promise をサポートしています。また、async / await 構文を使用することで、非同期コードを同期コードのように記述することができます。

Promise

function delay(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

delay(1000).then(() => console.log("1 second later"));

このコードでは、delay 関数が指定された時間だけ待機する Promise を返します。

async / await

async function asyncDelay() {
  await delay(1000);
  console.log("1 second later");
}

asyncDelay();

このコードでは、async / await を使用して非同期処理を同期的に記述しています。

非同期処理のメリット

  1. 非同期操作の簡素化: Promise と async / await を使用することで、非同期操作を簡素に記述できます。

  2. コードの可読性向上: async / await を使用することで、コールバック地獄を避け、コードの可読性が向上します。

  3. エラーハンドリングの統一: 非同期処理のエラーを try / catch 構文で統一的に扱うことができます。

デコレーター

function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${propertyKey} with arguments:`, args);
    return originalMethod.apply(this, args);
  };
}

class Calculator {
  @log
  add(a: number, b: number): number {
    return a + b;
  }
}

const calc = new Calculator();
console.log(calc.add(2, 3));

このコードでは、log デコレーターを使用して add メソッドの呼び出しをログに記録しています。

デコレーターのメリット

  1. コードのモジュール化: デコレーターを使用することで、横断的な関心事を分離してコードをモジュール化できます。

  2. 再利用性の向上: デコレーターを利用することで、同じ振る舞いを複数のクラスやメソッドに適用できます。

  3. コードの簡潔性: デコレーターを使用することで、追加の振る舞いを簡潔に記述できます。

型ガード

TypeScript では、型ガードを使用してランタイムに型をチェックし、適切な処理を行うことができます。

function isString(value: any): value is string {
  return typeof value === "string";
}

function printValue(value: string | number) {
  if (isString(value)) {
    console.log(`String: ${value}`);
  } else {
    console.log(`Number: ${value}`);
  }
}

printValue("Hello");
printValue(42);

このコードでは、isString 型ガードを使用して、value が string 型かどうかをチェックしています。

型ガードのメリット

  1. 安全な型変換: 型ガードを使用することで、ランタイムに安全に型変換を行うことができます。

  2. コードの可読性向上: 型ガードを使用することで、条件に基づいた処理を明確に記述できます。

  3. 型安全性の向上: 型ガードを使用することで、意図しない型エラーを防ぐことができます。

マップ型

TypeScript のマップ型を使用すると、既存の型をもとに新しい型を作成できます。これは、オブジェクトのプロパティを変換する場合に非常に便利です。

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

type ReadonlyPerson = Readonly<Person>;

const person: ReadonlyPerson = {
  name: "John",
  age: 30,
};

// person.age = 31; // エラー: 'age' は読み取り専用プロパティです

マップ型のメリット

  1. 型変換の簡素化: マップ型を使用することで、既存の型から簡単に新しい型を作成できます。

  2. 型安全性の向上: マップ型を使用することで、型変換が型安全に行われ、意図しない型エラーを防ぐことができます。

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

TypeScript は、ユニオン型とインターセクション型を使用して、複数の型を組み合わせることができます。

ユニオン型

function format(value: string | number) {
  if (typeof value === "string") {
    return value.toUpperCase();
  } else {
    return value.toFixed(2);
  }
}

console.log(format("hello"));
console.log(format(42));

このコードでは、format 関数が string または number 型の引数を受け取ります。

インターセクション型

type Person = {
  name: string;
};

type Employee = {
  id: number;
};

type EmployeePerson = Person & Employee;

const employee: EmployeePerson = {
  name: "John",
  id: 1,
};

console.log(employee);

このコードでは、Person と Employee 型を組み合わせた EmployeePerson 型を作成しています。

ユニオン型とインターセクション型のメリット

  1. 柔軟な型定義: ユニオン型とインターセクション型を使用することで、柔軟に型を定義できます。

  2. 型安全性の向上: 型の組み合わせを型安全に行うことで、意図しない型エラーを防ぐことができます。

まとめ

私は普段 Java を使っているのですが TS の方が型がより細かく指定できるのでこちらの方が良いと思いました。

Discussion