⌨️

改めてTypeScriptの型をまとめる

2024/07/15に公開

復習もかねて、TypeScriptの型をまとめていきます!!!

プリミティブ型

TypeScriptには、JavaScriptのプリミティブ型に対応する以下の型があります。これらは最も基本的な型でよく使う型です。

プリミティブ型は全部で7つです。

  • string
  • number
  • boolean
  • undefined
  • null
  • symbol
  • bigint

string型

文字列を表す型です。
シングルクォート、ダブルクォート、バッククォートで囲むことができます。

const message: string = "Hello, TypeScript!";
const name: string = 'John Doe';
const greeting: string = `Welcome, ${name}!`;

テンプレートリテラル(3つ目のバッククォートで囲まれた文字列)を使用すると、変数を文字列内に埋め込むことができます。

number型

数値を表す型です。
整数、浮動小数点数、2進数、8進数、16進数をサポートしています。

const decimal: number = 6;
const float: number = 3.14;
const binary: number = 0b1010;
const hex: number = 0xf00d;
const octal: number = 0o744;

boolean型

真偽値を表す型です。
trueまたはfalseをとります。

const isLoading: boolean = true;
const hasCompleted: boolean = false;

undefined型

値が未定義であることを表す型です。
変数の宣言のみだとundefinedが割り当てられます。

const u: undefined = undefined;

let x; // xは変数の宣言のみのためundefinedとなります

null型

値が無いことを表す型です。
nullはundefinedと違い、nullを明示的に代入しない限り割り当てられることはありません。

const n: null = null;

symbol型

一意で不変のプリミティブ値を表す型です。
値が同じでも別の変数との等価比較はfalseとなります。つまり、等価比較でtrueとなるのは自分自身との比較の場合のみです。

const sym1: symbol = Symbol("key");
const sym2: symbol = Symbol("key");
console.log(sym1 === sym2); // false

bigint型

number型よりも大きな(2の53乗)整数を扱うための型です。
数字の末尾に「n」をつけて初期化します。number型とbigint型での演算はできません。

const big: bigint = 100n;

object型

プリミティブ型ではない型は基本的にobject型を継承しています。
その中でもよく見る・使う型を列挙します。

object型

プリミティブ型以外のすべての型を表します。
通常は下記のような宣言はしません。後に紹介するインターフェースを用いてオブジェクトの形を明示的に宣言します。

const obj: object = { key: "value" };

array型

配列を表す型です。
number[],Array<string>のように配列の中身がどの型なのかも宣言します。

const numbers: number[] = [1, 2, 3, 4, 5];
const fruits: Array<string> = ["apple", "banana", "orange"];

tuple型

固定長の配列で、各要素の型が指定されている型です。

const tuple: [string, number] = ["TypeScript", 2024];
console.log(tuple[0]); // "TypeScript"
console.log(tuple[1]); // 2024

arrayとtupleは似ていますが以下の点で異なります。

要素数 要素の型
array 可変 配列全体で指定
tuple 固定 要素毎に指定
// 正しい使用法
const person: [string, number] = ["Alice", 30];

// エラー: 型が一致しない
const invalidPerson: [string, number] = [30, "Alice"];

// エラー: 要素数が多すぎる
const tooManyElements: [string, number] = ["Bob", 25, true];

タプルは、関数から複数の値を返す場合や、CSVファイルの行を表現する場合などに便利です。

enum型

列挙型を定義します。
enumは、デフォルトで0から始まる数値を各メンバーに割り当てますが、明示的に値を指定することもできます。

enum Color {
    Red,
    Green,
    Blue
}

console.log(Color.Green); // 1

// 値を割り当てたenum
enum Direction {
    Up = "UP",
    Down = "DOWN",
    Left = "LEFT",
    Right = "RIGHT"
}

console.log(Direction.Up); // "UP"

意味的に関連する要素をひとまとめにできるため、定数に使われたりします。

特殊な型

TypeScriptには、特殊な用途のための型もあります。

any型

任意のすべての型を許容する特殊な型です。
TypeScriptの型チェックが無効になるため、使用は避けるべきです。

let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // これもOK

unknown型

any型の型安全版です。
unknown型の値は、型チェックを行ってから使用する必要があります。

let userInput: unknown;
userInput = 5;
userInput = "hello";

if (typeof userInput === "string") {
    console.log(userInput.toUpperCase()); // OK
}

void型

関数が値を返さないことを示す型です。

function logMessage(message: string): void {
    console.log(message);
}

never型

決して発生しない値の型を表します。
never型には何も代入できません。

インターフェース

オブジェクトの形状を定義します。

  • ?をつけるとオプショナルとなり、そのプロパティが必須ではなくなります
  • readonlyをつけると読み取り専用となります
interface User {
  name: string;
  age: number;
  email?: string; // オプショナルなプロパティ
  readonly id: number; // 読み取り専用のプロパティ
}

// emailはオプショナルなため、あってもなくてもOK
const user: User = {
  name: "田中太郎",
  age: 30,
  id: 1
};

// user.id = 2; // エラー: idは読み取り専用プロパティ

インターフェースは継承することもできます

interface Shape {
  color: string;
}

interface Square extends Shape {
  sideLength: number;
}

const square: Square = {
  color: "blue",
  sideLength: 10
};

ユニオン型

複数の型のいずれかを表す型です。

type unionType = string | number;
// OK
const moji: unionType = "moji";
const suuji: unionType = 1;

// エラー:unionTypeにbooleanは含まれない
// const suuji: unionType = true;

ユニオン型は、異なる型を持つ可能性のある値を扱う場合に特に便利です:

function printId(id: number | string) {
  if (typeof id === "string") {
    console.log(id.toUpperCase());
  } else {
    console.log(id);
  }
}

printId(101); // OK
printId("202"); // OK

リテラル型

特定の値のみを許容する型です。
ユニオン型は複数ののいずれかを表しましたが、リテラル型は複数ののいずれかを表します。

// 文字列リテラル型
type Direction = "north" | "south" | "east" | "west";
const dir: Direction = "north"; // OK
// dir = "northeast"; // エラー

// 数値リテラル型
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
const roll: DiceRoll = 4; // OK
// roll = 7; // エラー

// 真偽値リテラル型
type YesNo = true | false;
const answer: YesNo = true; // OK
// answer = null; // エラー

constアサーション(as const)を使用すると、オブジェクのプロパティをリテラル型として扱うことができます。
また、constアサーションが為されたオブジェクトのプロパティはすべてreadonlyとなります。

const config = {
  apiUrl: "https://api.example.com",
  timeout: 3000
} as const;

// config.apiUrl の型は "https://api.example.com"
// config.timeout の型は 3000

ジェネリクス

ジェネリクスは動的に型を割り当てるための仕組みです。

基本的なジェネリクス

最も基本的なジェネリクスの例は、型パラメータを使用する関数です。
Tの部分がジェネリクスとなります。

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

const output1 = identity<string>("myString");  // 型は明示的に指定(string型)
const output2 = identity(42);  // 型は推論される(number型)

このidentity関数は、どのような型の引数でも受け取り、同じ型を返すことができます。

継承を持つジェネリクス

継承を持つジェネリクスを使うことで、型パラメータがある特定の条件を満たすことを保証できます。

interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);  // OK: T は Lengthwise を拡張しているので、length プロパティを持つ
  return arg;
}

loggingIdentity({length: 10, value: 3});  // OK
// loggingIdentity(3);  // エラー: number型にはlengthプロパティがない

条件型

条件型を使用すると、三項演算子のような分岐を用いて、入力の型に基づいて出力の型を決定できます。

type IsString<T> = T extends string ? true : false;

type T1 = IsString<"文字列">; //type T1 = true
type T2 = IsString<1>;  //type T2 = false

条件型は複雑な型の操作や、型の変換を行う際に非常に強力です。

まとめ

改めてTypeScriptの代表的な型についてまとめました。静的型付けは、書いていると面倒なことも多々ありますが、やはり安心感があって好きです!

TypeScriptには、他にもたくさん型が存在します。

  • Map
  • Set
  • Partial
  • Required
  • Record .......

TypeScriptに慣れるとついつい技巧的な技を使いたくなりがちですが、可読性を考慮するとコードは可能な限りシンプルに保つほうが圧倒的にメリットがあります。自分のエゴを捨ててコーディングに取り組みたいところです。

Discussion