【実務・中級編】TypeScript 5.xにおけるconst型パラメータの威力 – TypeScript コア・型システムの基礎解析バイブル

TypeScript 5.xでジェネリクスをリテラル型で保持する:`const` 型パラメータの真髄

Webエンジニア諸君、日々の開発お疲れ様です。テクニカルリードとして、皆さんがより堅牢で保守性の高いコードを書くための知見を共有できることを嬉しく思います。今回は、TypeScript 5.xで導入された `const` 型パラメータに焦点を当て、ジェネリクスで渡された値をリテラル型として保持するその強力な挙動と、実務でどのように応用できるかを深掘りしていきます。

なぜリテラル型を保持したいのか?

まず、なぜジェネリクスで渡された値をリテラル型として保持したいのか、その必要性を理解することから始めましょう。

JavaScriptでは、変数はその値の型(`string`、`number`、`boolean` など)を持ちますが、リテラル値(`”hello”`、`42`、`true` など)は、その具体的な値自体が型として機能することがあります。TypeScriptはこのリテラル型をサポートしており、より精密な型定義を可能にします。

例えば、ある関数が文字列を受け取るとします。

function processString(value: string) {
console.log(`Processing: ${value}`);
}

processString(“hello”); // OK
processString(“world”); // OK
processString(prompt(“Enter text:”) || “”); // OK, しかし入力値が何になるか不明確

ここで問題となるのは、`prompt` のような外部からの入力や、動的な値によって、期待しない文字列が渡される可能性があることです。しかし、もし関数が特定の「固定された」文字列のみを受け取るべき状況であれば、`string` 型では不十分です。`”success”`、`”error”`、`”pending”` のような限定された文字列の集合だけを許可したい場合、リテラル型が役立ちます。

type Status = “success” | “error” | “pending”;

function handleStatus(status: Status) {
console.log(`Status is: ${status}`);
}

handleStatus(“success”); // OK
handleStatus(“error”); // OK
// handleStatus(“failed”); // Error: Argument of type ‘”failed”‘ is not assignable to parameter of type ‘Status’.

このように、リテラル型はコードの意図を明確にし、不要な値の代入を防ぐことで、バグの早期発見に繋がります。

ジェネリクスとリテラル型:従来の課題

ジェネリクスは、型を抽象化し、再利用可能なコンポーネントを作成するための強力な機能です。しかし、従来のジェネリクスでは、ジェネリック型パラメータに渡されたリテラル値を、そのリテラル型として厳密に保持することが難しい場面がありました。

例えば、以下のような関数を考えます。

function createArray(element: T, length: number): T[] {
return Array(length).fill(element);
}

const stringArray = createArray(“hello”, 3); // stringArray の型は string[]
const numberArray = createArray(123, 2); // numberArray の型は number[]

この `createArray` 関数では、`T` は `string` や `number` のような一般的な型として推論されます。もし `createArray` に `”hello”` というリテラル値を渡したとしても、`T` として `string` 型が推論され、`”hello”` というリテラル型そのものは保持されません。

// “hello” というリテラル型を期待したいが…
const specificStringArray = createArray(“hello”, 3);
// specificStringArray の型は string[] と推論されてしまう。
// `”hello”[]` のような型にはならない。

この挙動は、例えば特定のキー名や状態を表す文字列を配列として保持したい場合に、期待通りの型推論が得られないという課題を生じさせます。

`const` 型パラメータの登場:リテラル型を「そのまま」保持する

TypeScript 5.0 以降で導入された `const` 型パラメータは、この課題を劇的に解決します。`const` 型パラメータをジェネリック型パラメータの宣言に付与することで、渡された引数のリテラル値を、そのリテラル型として厳密に保持させることができます。

構文は非常にシンプルです。

function createArrayConst(element: T, length: number): T[] {
return Array(length).fill(element);
}

`const T` と宣言することで、`T` には渡された引数の「最も具体的な」型、すなわちリテラル型が推論されます。

実演:`const` 型パラメータの威力

実際に `const` 型パラメータを使った場合とそうでない場合を比較してみましょう。

1. `const` 型パラメータなしの場合(従来)

function createArrayNoConst(element: T, length: number): T[] {
return Array(length).fill(element);
}

const resultNoConst = createArrayNoConst(“hello”, 3);

// resultNoConst の型推論結果: string[]
console.log(resultNoConst); // [“hello”, “hello”, “hello”]

// type assertion をしないと、”hello” というリテラル型は保持されない
const literalString: “hello” = “hello”;
// const incorrectString: “hello” = “world”; // Error

2. `const` 型パラメータありの場合

function createArrayWithConst(element: T, length: number): T[] {
return Array(length).fill(element);
}

const resultWithConst = createArrayWithConst(“hello”, 3);

// resultWithConst の型推論結果: “hello”[]
console.log(resultWithConst); // [“hello”, “hello”, “hello”]

// ここで、resultWithConst の要素は “hello” というリテラル型として扱われる
const firstElement: “hello” = resultWithConst[0];
console.log(firstElement); // “hello”

// もし異なるリテラル値を渡せば、それに応じた型が推論される
const resultNumber = createArrayWithConst(42, 2);
// resultNumber の型推論結果: number[] (42 はリテラル型だが、number というより広範な型も内包するため)

const resultBoolean = createArrayWithConst(true, 1);
// resultBoolean の型推論結果: boolean[]

const resultSpecificBoolean = createArrayWithConst(true as const, 1);
// resultSpecificBoolean の型推論結果: true[]

重要なポイント:

  • `createArrayWithConst(“hello”, 3)` の場合、`T` は `”hello”` というリテラル型に推論されます。結果として `resultWithConst` の型は `”hello”[]` となります。
  • `createArrayWithConst(42, 2)` の場合、`42` はリテラル値ですが、TypeScript は `number` 型として推論することが多いです。これは、`42` が `number` 型の集合の一部であるため、より汎用的な型として扱われるからです。しかし、`const` を付けることで、よりリテラルに近い挙動を期待できます。
  • `createArrayWithConst(true as const, 1)` のように、明示的に `as const` を使用すると、`true` のリテラル型が `T` に推論され、結果として `true[]` という型が得られます。これは、`const` アサーションがリテラル値を最も具体的な型として扱うためです。

実務での応用パターン:堅牢なフロントエンド設計

`const` 型パラメータの威力は、単なる型推論の改善にとどまらず、実務における堅牢な設計パターンを可能にします。

パターン 1: UIコンポーネントの定義と状態管理

UIコンポーネントのpropsや状態管理において、特定の文字列リテラルで表現される状態やタイプを厳密に定義したい場面は頻繁にあります。

例: ボタンのタイプ定義

// 許容されるボタンタイプをリテラル型で定義
type ButtonType = “primary” | “secondary” | “danger”;

// コンポーネントに渡されるpropsの型
interface ButtonProps {
label: string;
type: ButtonType; // ここで ButtonType を使用
}

// ボタンコンポーネント(簡略化)
function Button({ label, type }: ButtonProps) {
return ;
}

// — 使用例 —
const primaryButton =

タイトルとURLをコピーしました