TypeScriptの基礎 - 型注釈とプリミティブ型

提供: MochiuWiki : SUSE, EC, PCB

概要

TypeScriptは、JavaScriptに静的型付けを追加したプログラミング言語であり、コードの品質と保守性を向上させる。
その中心となる概念が 型注釈 (Type Annotation) と である。

TypeScriptの基本的な型システムを構成する要素を以下に示す。

  • 型注釈 (Type Annotation)
    変数、関数の引数、戻り値に対して型を明示的に指定する構文
  • プリミティブ型
    string、number、boolean、null、undefined、symbol、bigintの7種類
  • リテラル型
    特定の値そのものを型として扱う仕組み
  • 特殊な型
    any、unknown、void、neverといった特殊な用途を持つ型


TypeScriptの型システムを理解することで、バグを早期に発見し、IDEのコード補完や静的解析の恩恵を最大限に活用できる。

型注釈の省略と型推論については、TypeScriptの基礎 - 型推論のページを参照すること。


型注釈 (Type Annotation)

型注釈とは、変数や関数に対してデータの型を明示的に指定する構文である。
コロン (:) の後に型名を記述することにより、TypeScriptコンパイラに型情報を伝える。

変数の型注釈

変数の型注釈は、変数名の直後にコロンと型名を記述する。

基本的な構文は以下の通りである。

 let <変数名>: <型名> = <初期値>;


具体的な例を以下に示す。

 let userName: string  = "Alice";
 let age: number       = 30;
 let isActive: boolean = true;
 let score: number;  // 初期値なしも可能 (undefinedになる)


型注釈が必要な場面と省略できる場面を以下にまとめる。

型注釈の要否
区分 場面 説明
型注釈が必要な場面 関数のパラメータ TypeScriptは関数の引数の型を推論しないため、明示的な型注釈が必要
公開APIの戻り値 ライブラリや公開インターフェースでは意図を明確にするため記述する。
複雑なオブジェクト 型推論では不十分な場合に明示する。
型注釈を省略できる場面 変数の初期化時 初期値から型推論が可能なため、省略しても同等の型安全性が得られる。
単純な関数の戻り値 実装から型推論が可能な場合
コールバック関数の引数 高階関数の型定義から推論される場合



関数の引数の型注釈

関数のパラメータは、TypeScriptが自動推論を行わないため、型注釈が必須である。

 function greet(name: string): void {
    console.log(`Hello, ${name}!`);
 }
 
 function add(a: number, b: number): number {
    return a + b;
 }
 
 // 複数の型を受け入れる場合 (ユニオン型)
 function display(value: string | number): void {
    console.log(value);
 }


型注釈が正しく機能すると、誤った型の引数を渡した時にコンパイル時にエラーが発生する。

 greet(42);  // エラー: Argument of type 'number' is not assignable to parameter of type 'string'.


関数の戻り値の型注釈

関数の戻り値の型は、引数リストの閉じ括弧の後にコロンと型名を記述する。

 // 戻り値がnumberの場合
 function multiply(a: number, b: number): number {
    return a * b;
 }
 
 // 戻り値がない場合はvoid
 function logMessage(message: string): void {
    console.log(message);
 }
 
 // 戻り値がstring または nullの場合
 function findUser(id: number): string | null {
    if (id === 1) {
       return "Alice";
    }
    return null;
 }


戻り値の型注釈は、関数の意図を明確にし、誤った値を返した場合にコンパイル時にエラーを発生させる。


プリミティブ型

TypeScriptのプリミティブ型は、JavaScriptのプリミティブ型に対応する7種類の基本的な型である。
これらは、TypeScriptの型システムの基盤となる。

string

string 型は、文字列値を表す型である。
シングルクォート、ダブルクォート、バッククォート (テンプレートリテラル) のいずれで記述した文字列も string 型となる。

 let firstName: string = "Alice";
 let lastName: string  = 'Bob';
 let fullName: string  = `${firstName} ${lastName}`;  // テンプレートリテラルも string 型
 
 let greeting: string  = `Hello, ${firstName}!`;


文字列の操作に関するメソッド (length、toUpperCase、slice 等) も string 型の値に対して利用できる。

number

number 型は、全ての数値を表す型である。
JavaScriptと同様に、整数と浮動小数点数の区別はなく、全て number 型で扱われる。

 let integer: number  = 42;
 let float: number    = 3.14;
 let negative: number = -10;
 
 // 各種数値リテラルもnumber型
 let hex: number    = 0xFF;    // 16進数
 let binary: number = 0b1010;  // 2進数
 let octal: number  = 0o777;   // 8進数
 
 // 特殊なnumber値
 let infinity: number   = Infinity;
 let notANumber: number = NaN;


非常に大きな整数を扱う場合は、number 型では精度が失われる可能性があるため、bigint 型の使用を検討する。

boolean

boolean 型は、真偽値を表す型であり、true または false のいずれかの値を持つ。

 let isActive: boolean    = true;
 let isCompleted: boolean = false;
 
 // 比較演算の結果もboolean型
 let isAdult: boolean       = age >= 18;
 let hasPermission: boolean = role === "admin";


条件分岐 (if 文) や ループ (while 文) の条件式で使用される。

null と undefined

nullundefined は、TypeScriptにおいて別々の型として扱われる。

undefined と null の違い
説明
undefined 値が未定義の状態を表す。
変数宣言後に値が代入されていない場合などに発生する。
null 値がないことを明示的に表す。
意図的に 値がない 状態を示す場合に使用する。


 let notDefined: undefined = undefined;
 let noValue: null         = null;


tsconfig.jsonファイルstrictNullChecks が有効な場合 (推奨設定)、nullundefined は他の型に代入できない。

 // strictNullChecksが有効な場合
 let name: string = null;             // エラー: Type 'null' is not assignable to type 'string'.

 // ユニオン型で明示的にnullを許可する
 let userName: string | null = null;  // OK
 userName = "Alice";                  // OK


strictNullChecks の詳細は、TypeScriptの基礎 - tsconfig.jsonのページを参照すること。

symbol

symbol 型は、Symbol() 関数で生成されるユニークな識別子を表す型である。
同じ説明を持つシンボルでも、異なるインスタンスは等しくならないという特性を持つ。

 let sym1: symbol = Symbol("description");
 let sym2: symbol = Symbol("description");
 
 console.log(sym1 === sym2);  // false (異なるインスタンスは常に不等)
 
 // オブジェクトのユニークなプロパティキーとして使用
 const KEY = Symbol("key");
 const obj = {
    [KEY]: "value"
 };


symbol 型は、プロパティキーの衝突を防ぐ用途や、メタプログラミングに利用される。

bigint

bigint 型は、ES2020以降で利用可能な型であり、number 型では表現できないほど大きな整数を扱う。

リテラル構文は、数値の末尾に n を付与する形式である。

 let bigNumber: bigint = 100n;
 let veryLarge: bigint = 9007199254740991n;
 
 // BigInt()関数でも生成可能
 let fromFunction: bigint = BigInt(100);


bigintnumber は別の型であり、直接の算術演算は行えない点に注意が必要である。

 let n: number = 10;
 let b: bigint = 20n;
 
 let result = n + b;  // エラー: Operator '+' cannot be applied to types 'number' and 'bigint'.


bigint 型を使用するには、tsconfig.jsonファイルtargetES2020 以降に設定する必要がある。


リテラル型

リテラル型は、TypeScriptの型システムにおける強力な機能の1つである。
特定の値を型として使用することにより、より厳密な型制約を表現できる。

リテラル型とは

リテラル型とは、特定の値そのものを型として扱う仕組みである。
例えば、string 型があらゆる文字列を表す のに対し、文字列リテラル型 "left""left" という値のみ を表す。

 // string型 : あらゆる文字列を受け入れる
 let direction1: string = "left";
 direction1 = "anything";   // OK
 
 // リテラル型 : "left"という値のみを受け入れる
 let direction2: "left" = "left";
 direction2 = "right";      // エラー: Type '"right"' is not assignable to type '"left"'.


リテラル型はユニオン型 (|) と組み合わせて使用することにより、取り得る値を限定した型を定義できる。

文字列リテラル型

文字列リテラル型は、特定の文字列値のみを受け入れる型である。
ユニオン型と組み合わせて、列挙的な制約を表現するために広く使用される。

 // ユニオン型による文字列リテラル型
 type Direction = "left" | "right" | "center";
 type Status = "active" | "inactive" | "pending";
 
 function setAlignment(alignment: Direction): void {
    console.log(`Alignment: ${alignment}`);
 }
 
 setAlignment("left");    // OK
 setAlignment("center");  // OK
 setAlignment("top");     // エラー: Argument of type '"top"' is not assignable to parameter of type 'Direction'.


文字列リテラル型により、無効な値の使用をコンパイル時に検出できる。

数値リテラル型

数値リテラル型は、特定の数値のみを受け入れる型である。
サイコロの目や特定のコードの値など、取り得る数値が限定される場面で活用する。

 // サイコロの目を表す型
 type DiceValue = 1 | 2 | 3 | 4 | 5 | 6;
 
 function rollDice(): DiceValue {
    return (Math.floor(Math.random() * 6) + 1) as DiceValue;
 }
 
 let result: DiceValue  = 3;  // OK
 let invalid: DiceValue = 7;  // エラー: Type '7' is not assignable to type 'DiceValue'.


真偽値リテラル型

真偽値リテラル型は、true または false という具体的な値を型として扱う。
条件によって型が異なる場合や、特定の状態を型レベルで表現する際に使用する。

 type AlwaysTrue  = true;
 type AlwaysFalse = false;
 
 // 成功・失敗を型で表現する例
 type SuccessResult = { success: true; data: string };
 type FailureResult = { success: false; error: string };
 type Result = SuccessResult | FailureResult;
 
 function processResult(result: Result): void {
    if (result.success) {
       console.log(result.data);   // TypeScriptは、result.dataがstringと認識する
    }
    else {
       console.log(result.error);  // TypeScriptは、result.errorがstringと認識する
    }
 }


let と const での型推論の違い

letconst では、型注釈を省略した場合の型推論の結果が異なる。
この動作は、型ワイドニング (Type Widening) と呼ばれる。

 // let : ワイドな型に推論される
 let color = "red";     // 型: string
 color = "blue";        // OK (string 型の任意の値を代入可能)
 
 // const: リテラル型に推論される
 const theme = "dark";  // 型: "dark" (再代入不可のためリテラル型)


let の場合、変数は後から別の値に変更される可能性があるため、TypeScriptは広い型 (string) に推論する。
const の場合、変数は再代入されないことが確定しているため、TypeScriptはリテラル型 ("dark") に推論する。

let と constの型推論の違い
宣言 コード例 推論される型 理由
let let color = "red" string 再代入の可能性があるためワイドな型
const const theme = "dark" "dark" 再代入不可のためリテラル型
let let count = 0 number 再代入の可能性があるためワイドな型
const const MAX = 100 100 再代入不可のためリテラル型


型ワイドニングの詳細な仕組みと制御方法は、TypeScriptの基礎 - 型推論のページを参照すること。


特殊な型

TypeScriptには、特殊な用途のために設けられた型が存在する。
これらの型は、通常のプリミティブ型では表現できない状態や振る舞いを扱うために使用する。

any

any 型は、全ての型チェックを無効化する型である。
any 型の変数には、どんな値でも代入でき、どんな操作も許可される。

 let value: any = 42;
 value          = "hello";    // OK
 value          = true;       // OK
 value          = [1, 2, 3];  // OK
 
 // any型の変数にはどんな操作も許可される (エラーが発生しない)
 value.someMethod();          // OK (実行時エラーの可能性あり)
 value.nonExistentProp;       // OK (実行時エラーの可能性あり)


any 型を使用すると、TypeScriptの型安全性が完全に失われる。
型チェックが無効化されることで、実行時エラーの早期発見ができなくなるため、使用は最小限に留めることを推奨する。

any 型が許容される場面を以下に示す。

  • 段階的なJavaScriptからTypeScriptへの移行時
    既存コードを一時的に any でラップして移行を進める場合
  • 型定義が存在しないサードパーティライブラリの使用時
    @types パッケージが存在しない場合の暫定的な対処


unknown

unknown 型は、any 型と同様に任意の値を受け入れるが、型安全性を保持する 型安全なany である。
unknown 型の変数を使用する前に、typeof 等による型チェックが必須となる点が any との大きな違いである。

 let value: unknown = "hello";
 
 // unknown型の変数は使用前に型チェックが必要
 value.toUpperCase();     // エラー: Object is of type 'unknown'.
 
 // typeofによる型チェック後は使用可能
 if (typeof value === "string") {
    value.toUpperCase();  // OK (string型として扱われる)
 }


unknown 型は、外部からの入力等で型が不確定な場面で安全に使用できる。

 // catch句でのエラーは、unknown型 (TypeScript 4.0以降)
 try {
    throw new Error("something went wrong");
 }
 catch (error: unknown) {
    if (error instanceof Error) {
       console.log(error.message);  // OK
    }
 }
 
 // JSON.parseの結果等、型が不確定な場合
 function parseData(jsonString: string): unknown {
    return JSON.parse(jsonString);
 }


下表に、anyunknown の違いを示す。

any と unknown の比較
特性 any unknown
任意の値の代入 可能 可能
使用前の型チェック 不要 必須
型安全性 失われる 保持される
推奨度 使用を避ける 型が不確定な場面で使用


unknown 型を用いた型の絞り込み (Type Narrowing) の詳細は、TypeScriptの基礎 - 型の絞り込みのページを参照すること。

void

void 型は、戻り値がない関数の戻り値型として使用する型である。
undefined のみを代入可能であり、関数が値を返さないことを明示する。

 // 戻り値がない関数
 function logMessage(message: string): void {
    console.log(message);
    // return undefined; は暗黙的に行われる
 }
 
 // void型の変数には、undefinedのみ代入可能
 let result: void = undefined;  // OK
 let invalid: void = "hello";   // エラー: Type 'string' is not assignable to type 'void'.


void と undefinedは似ているが、voidは 関数が意図的に値を返さない という意味を明示するために使用する。

never

never 型は、到達不可能な値の型である。
例外をスローする関数や無限ループを持つ関数の戻り値型として使用され、その処理が正常に終了しないことを表す。

 // 必ず例外をスローする関数
 function throwError(message: string): never {
    throw new Error(message);
 }
 
 // 無限ループ
 function infiniteLoop(): never {
    while (true) {
       // 処理
    }
 }


never 型の重要な活用例として、網羅性チェック (exhaustiveness check) がある。
switch文で全てのケースを処理していることをコンパイル時に検証できる。

 type Shape = "circle" | "square" | "triangle";
 
 function getArea(shape: Shape): number {
    switch (shape) {
       case "circle":
          return Math.PI * 10 * 10;
       case "square":
          return 10 * 10;
       case "triangle":
          return (10 * 10) / 2;
       default:
          // shape が never 型になることで、全ケースが処理されていることを保証
          const exhaustiveCheck: never = shape;
          throw new Error(`Unknown shape: ${exhaustiveCheck}`);
    }
 }


新しい型 (例: "pentagon") が Shape に追加されて、switch文のケースが追加されていない場合、<br default節でshapeが"pentagon"型になるため、neverへの代入でコンパイルエラーが発生する。
これにより、型の追加漏れを確実に検出できる。


型注釈の省略と型推論

TypeScriptは、変数の初期化値や文脈から型を自動的に推論する 型推論 の仕組みを持つ。
型推論が機能する場面では、型注釈を省略しても同等の型安全性が得られる。

 let x   = 5;        // 型推論: number
 const y = "hello";  // 型推論: "hello" (リテラル型)
 let z   = true;     // 型推論: boolean


型推論の詳細な仕組みや、型ワイドニング、contextual typing (文脈的型付け) については TypeScriptの基礎 - 型推論 で詳しく解説している。


関連情報