目次
- はじめに
- 型推論とは?
-
リテラル型とは
・constの場合
・letの場合
・varの場合 - 比較まとめ
- こんな時に差が出る
-
明示的に型を指定する方法
・1.型注釈を使う方法
・2.as-constアサーションを使う方法
・比較例
・注意点
・補足
・いつ使うべき - まとめ
- 次回予告
はじめに
TypeScriptは型を明示的に書かなくても、型推論(型を自動で判断)してくれる便利な機能があります。
でも「let」「const」「var」の使い方によって、推論される型が微妙に違うのをご存知ですか?
この記事では、TypeScriptの型推論と変数宣言のキーワードの関係について、サンプルコードと一緒に分かりやすく解説します!
型推論とは?
まずは「型推論」って何?というところから。
たとえば、以下のように変数を定義すると
let message = "Hello, world!";
let message: string //TypeScriptが推論した型の表示
TypeScriptは「この変数には文字列が入っているから、型は string だな」と自動で判断します。
正確には、初期化時の値を基にその変数に最も適切な型を自動で推論します。これが型推論です。
型を書かなくても、TypeScriptがいい感じに判断してくれるわけですね。
下記の場合はどうでしょうか?
const greeting = "Hello";
const greeting: "Hello" //TypeScriptが推論した型の表示
なんと、リテラル型(文字列そのもの)になります!
これはconstで宣言された変数は再代入できないため、TypeScriptがより具体的な型(リテラル型)を推論するためです。
リテラル型とは?
「文字列」ではなく、「特定の値そのもの」を型として持つものです。
constの場合:リテラル型になる
const age = 42;
この場合、型は 42(number ではなくリテラル型)となる
リテラルを使用するメリット
・コードの可読性向上:直接的な値を示すことで、プログラムの意図が明確になります.
・デバッグの容易さ:値が直接記述されているため、どこで値が変更されたのかを特定しやすくなります.
・パフォーマンスの向上:変数に値が格納されるよりも、リテラルを使用する方が処理速度が速くなる場合があります.
letの場合:一般的な型になる
let greeting = "Hello";
こちらはどうでしょう?
let greeting: string //TypeScriptが推論した型の表示
今度はリテラル型ではなく、一般的な string型になりました。
これは「let は後で値を変更するかもしれない」と思われるからです。
varの場合:letと同じく一般的な型
var greeting = "Hello";
推論される型は
var greeting: string //TypeScriptが推論した型の表示
let と同じく、一般的な型になります。
ただし、var はスコープが関数単位だったり巻き上げが起きたり、重複宣言できたりとクセが強いので、最近は基本的に使われません。
比較まとめ
宣言方法 | 例 | 推論される型 | 補足 |
---|---|---|---|
const | const a = "hi" | hi(リテラル型) | 値の変更が不可能 |
let | let b = "hi" | string | 値の変更が可能 |
var | var c = "hi" | string | 値の変更が可能(※古い書き方。let推奨) |
特性 | var | let | const |
---|---|---|---|
初期化 | ✅なくてもOK | ✅ なくてもOK | ❌ ないとNG |
再代入 | ✅ できる | ✅ できる | ❌ できない |
再宣言(同一スコープ内) | ✅ できる | ❌ できない | ❌ できない |
スコープ | 関数スコープ | ブロックスコープ | ブロックスコープ |
巻き上げ(※Hoisting) | ✅ 起きる(※undefined) | ✅ 起きるが初期化前はアクセス不可 (※TDZ) | ✅ 起きるが初期化前はアクセス不可 (※TDZ) |
こんな時に差が出る!
たとえば、関数にリテラル型だけを受け取ってほしい場合
function greet(word: "Hello") {
console.log(word);
}
const a = "Hello";
let b = "Hello";
greet(a); // OK
greet(b); // エラー
const の場合は "Hello" というリテラル型が推論されるので通りますが、
let の場合は string 型なのでエラーになります。
明示的に型を指定する方法
もし let や var でもリテラル型を使いたい場合は、型を明示することができます。
1.型注釈を使う方法
let greeting: "Hello" = "Hello";
var message: "hi" = "hi";
2.as constアサーションを使う方法
let greeting = "Hello" as const; // "Hello"型になる
let status = "success" as const; // "success"型になる
as constの特徴と使いどころ
as constは const assertion ( const アサーション)と呼ばれ、TypeScript 3.4で導入された機能です。
特徴
・値を最も厳密な型(リテラル型)として扱う。
・letやvarでも、constのようにリテラル型を得られる。
・型注釈よりも値から自動で型を推論してくれるため、タイプミスが起きにくい。
比較例
// 型注釈の場合:型とバリューを両方書く必要がある
let method1: "GET" = "GET";
// as const の場合:値から型が自動推論される
let method2 = "GET" as const;
// どちらも同じ "GET" 型になるが、as const の方が簡潔
注意点
ts// うっかりタイプミス
let method: "GET" = "GTE"; // エラー
// as const なら値から型が決まるので、こういうミスは起きない
let method = "GET" as const; // 必然的に "GET" 型になる
補足:型注釈によってリテラル型チェックが効かなくなる場合
TypeScriptでは、リテラル型を活かすには型注釈やアサーションの使い方に注意が必要です。
以下のようなコードでは、型注釈 any や never を使うことで、リテラル型チェックがすり抜けてしまいます。
function greet(word: 'Hello') {
console.log(word);
}
const hello = 'Hello'; // ✅ const → リテラル型 'Hello' に推論される
let a: any = 'Hello'; // ⚠️ any型 → 型チェックが効かず通る
let b = 'Hello' as never; // ⚠️ never型 → 絶対に呼ばれない値として扱われる
greet(hello); // ✅ OK
greet(a); // ⚠️ 型チェックが効かない
greet(b); // ⚠️ 実行されることは想定されていない
なぜ通るのか?
・any は「すべての型との互換性を持つ」ため、関数の型注釈をバイパスしてしまいます。
・never は「到達不能な値」ですが、代入時に TypeScript が特別扱いしてしまうため、一見通ってしまうように見えます。
対策
・anyを使うと型の恩恵を失ってしまうため、極力避けるのがベスト。
・never は主に「例外や無限ループ」の結果として使うべきで、意図的に代入するような使い方は避けるべきです。
このように、意図しない型抜けを防ぐためにも、as const と同様に 型注釈の使い方に慎重になることが重要です。
いつ使うべき?
・設定値やステータスなど、変更されない値
・Union型の判別で使う値
※ただし、多くの場合は素直にconstを使う方が自然です。as constは「letを使いたい特別な理由があるとき」の解決策として覚えておくと良いでしょう。
まとめ
・TypeScriptの型推論は、宣言方法によって挙動が変わる
・const → リテラル型(変更不可な値)
・let / var → 一般的な型(変更可能な値)
・型の厳密さが必要な場合は、const + リテラル型が強い!
参考リンク
・TypeScript公式:Type Inference
・TypeScript Deep Dive
・サバイバルTypeScript
・TypeScript公式ドキュメント(any型)
・TypeScript公式ドキュメント(never型)
注釈
・巻き上げ(Hoisting)
・TDZ
・Undefined
次回予告
今回はプリミティブ型(文字列・数値)での型推論を中心に解説しましたが、オブジェクトや配列の型推論はもう少し複雑です。
オブジェクトと配列での型推論について詳しく解説予定です。