メインコンテンツまでスキップ

特殊な型

この章では、TypeScriptの特殊な型について学びます。

この章で学ぶこと

  • any型とは何か、なぜ避けるべきか
  • unknown型を使った安全な型チェック
  • void型とnever型の違いと使いどころ
  • nullとundefinedの扱い方
備考

この章の内容は、型安全性を高めるために重要です。特にany型の危険性を理解し、unknown型を使った安全な代替方法を学びます。

any型(型チェックを無効化する型)

any型は、あらゆる型の値を受け入れる特殊な型です。TypeScriptの型チェックを無効化します。

// any型: どんな値でも代入できる
let value: any = 10;
value = 'Hello'; // OK
value = true; // OK
value = [1, 2, 3]; // OK
value = { name: 'Alice' }; // OK

// any型の変数に対してはどんな操作も許可される
let anyValue: any = 'Hello';
anyValue.toUpperCase(); // OK(実行時は動作する)
anyValue.nonExistentMethod(); // コンパイルエラーなし(実行時エラー)

any型の問題点

any型を使うと、TypeScriptの型チェックが無効になり、バグを見逃してしまいます。

let userData: any = { name: 'Alice', age: 25 };

// 存在しないプロパティにアクセスしてもエラーにならない
console.log(userData.email); // undefined(コンパイルエラーなし)

// 型の間違いに気づけない
let age: number = userData.age; // OK
let wrongAge: number = userData.name; // OK(コンパイルは通るが実行時に問題)

暗黙的any

型注釈がなく、型推論もできない場合は暗黙的anyになります。

// 型注釈がなく、初期値もない場合は暗黙的anyになる
let implicitAny; // 型はany
implicitAny = 10;
implicitAny = 'Hello';

// 関数の引数も型注釈がないとanyになる
function add(a, b) { // aとbはany型
return a + b;
}

add(5, 10); // OK
add('5', '10'); // OK(意図しない動作の可能性)

noImplicitAnyオプション

tsconfig.jsonnoImplicitAny: trueを設定すると、暗黙的anyを禁止できます。

// tsconfig.json
{
"compilerOptions": {
"noImplicitAny": true
}
}
// noImplicitAny: true の場合
function add(a, b) { // Error: Parameter 'a' implicitly has an 'any' type.
return a + b;
}

// 修正: 型注釈を追加
function addFixed(a: number, b: number): number {
return a + b;
}

any型を使うべき場面(最小限に)

// 1. 外部ライブラリで型定義がない場合
let externalLib: any = require('some-old-library');

// 2. 段階的な型付けを行う場合(一時的にanyを使い、後で正確な型に変更)
let temporaryAny: any = getData();
// 後で型を明確にする
let user: User = temporaryAny as User;
警告

any型はTypeScriptの型安全性を損ないます。できる限り使用を避け、後述のunknown型を使いましょう。

unknown型(型安全なany)

unknown型は、any型と似ていますが、より安全な型です。どんな値でも代入できますが、使用する前に型チェックが必要です。

// unknown型: どんな値でも代入できる
let value: unknown = 10;
value = 'Hello'; // OK
value = true; // OK
value = [1, 2, 3]; // OK

anyとの違い

// any型: 型チェックなしで使用できる(危険)
let anyValue: any = 'Hello';
let str1: string = anyValue; // OK(チェックなし)
str1.toUpperCase(); // OK

// unknown型: 型チェックが必要(安全)
let unknownValue: unknown = 'Hello';
// let str2: string = unknownValue; // Error: 型チェックが必要

// 型ガードで型を確認してから使用
if (typeof unknownValue === 'string') {
let str2: string = unknownValue; // OK(型が確定)
str2.toUpperCase(); // OK
}

unknown型の使用パターン

// パターン1: typeof による型チェック
function processValue(value: unknown): void {
if (typeof value === 'string') {
console.log(value.toUpperCase()); // string型のメソッドが使える
} else if (typeof value === 'number') {
console.log(value.toFixed(2)); // number型のメソッドが使える
} else if (typeof value === 'boolean') {
console.log(value ? 'Yes' : 'No');
}
}

processValue('hello'); // 'HELLO'
processValue(3.14159); // '3.14'
processValue(true); // 'Yes'

// パターン2: instanceof による型チェック
function processDate(value: unknown): void {
if (value instanceof Date) {
console.log(value.toISOString()); // Date型のメソッドが使える
}
}

// パターン3: in演算子による型チェック
function processUser(value: unknown): void {
if (
typeof value === 'object' &&
value !== null &&
'name' in value
) {
console.log((value as { name: string }).name);
}
}

実践的な例

// APIからのレスポンスを処理する関数
function processApiResponse(response: unknown): void {
// 型ガードで安全に型を確認
if (
typeof response === 'object' &&
response !== null &&
'data' in response
) {
console.log((response as { data: unknown }).data);
} else {
console.error('Invalid response format');
}
}

// JSONパース
function parseJSON(jsonString: string): unknown {
try {
return JSON.parse(jsonString); // unknown型を返す
} catch (error) {
return null;
}
}

let data: unknown = parseJSON('{"name": "Alice"}');

// 使用前に型を確認
if (data && typeof data === 'object' && 'name' in data) {
console.log((data as { name: string }).name);
}

anyとunknownの使い分け

状況推奨される型理由
外部APIのレスポンスunknown型が不明確なので安全性を優先
JSONパース結果unknown構造が不明なので型チェックが必要
古いコードの移行any → unknown段階的に型安全性を高める
どうしても型が分からないunknownanyより安全
警告

2026年の原則: any より unknown を第一選択肢にする

スキーマバリデーションライブラリ(Zod / Valibot / class-validator 等)が普及し、外部データは必ずスキーマで検証して型を確定させるのが主流になりました。any は型安全性を無効化するため、新規コードでは原則禁止と考えてください。

  • ✅ 外部データ(API レスポンス / JSON / user input)は unknown で受け取り、スキーマバリデーション後に具体型にする
  • ✅ 古いコード移行中は unknown に置き換え、段階的に型を絞り込む
  • ❌ 「とりあえず動かすため」の any は技術的負債を増やすだけなので避ける
  • any[]Record<string, any> ではなく unknown[] / Record<string, unknown> を使う

ESLint では @typescript-eslint/no-explicit-any ルール、Biome では lint/suspicious/noExplicitAny ルールを有効化して静的検出できます。

void型(値がないことを示す型)

void型は、値が存在しないことを表す型です。主に関数の戻り値で使用します。

// void型: 戻り値がない関数
function logMessage(message: string): void {
console.log(message);
// return文がない、またはreturn;だけ
}

// 使用例
logMessage('Hello, TypeScript!'); // コンソールに出力されるだけ

voidとundefinedの違い

// void型の関数
function voidFunction(): void {
console.log('This returns nothing');
// return; これもOK(何も返さない)
}

// undefined型の関数
function undefinedFunction(): undefined {
console.log('This returns undefined');
return undefined; // undefinedを返すことを明示(TypeScript 5.1以降はreturn文の省略も可)
}

// 型の互換性
let v: void = undefined; // OK(undefinedはvoidに代入できる)
// let u: undefined = voidFunction(); // Error(voidはundefinedに代入できない)

実践的な使用例

// イベントハンドラ
function handleClick(event: MouseEvent): void {
console.log('Button clicked');
event.preventDefault();
}

// コールバック関数の型定義
type Callback = (item: number) => void;

function forEach(arr: number[], callback: Callback): void {
for (let item of arr) {
callback(item);
}
}

forEach([1, 2, 3], (num) => {
console.log(num); // 戻り値は不要
});

// 非同期処理
async function fetchData(): Promise<void> {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
// 値を返さない(副作用だけ)
}

never型(決して値を返さない型)

never型は、決して値を返さない関数や、到達しない地点を表す型です。

// 例1: 例外をスローする関数(正常に終了しない)
function throwError(message: string): never {
throw new Error(message);
// この後のコードは実行されない
}

// 例2: 無限ループ
function infiniteLoop(): never {
while (true) {
console.log('This will never end');
}
// ここには到達しない
}

neverとvoidの違い

// void: 値を返さないが、関数は正常に終了する
function voidFunc(): void {
console.log('Done');
// 関数は終了する
}

// never: 関数が決して終了しない
function neverFunc(): never {
throw new Error('Never returns');
// 関数は終了しない(例外で中断)
}

never型の使用例:網羅性チェック

never型の重要な使い方として、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:
// すべてのケースを処理していれば、ここはnever型になる
const exhaustiveCheck: never = shape;
throw new Error(`Unhandled shape: ${exhaustiveCheck}`);
}
}

新しいshapeを追加した場合、コンパイル時にエラーが発生します。

type Shape2 = 'circle' | 'square' | 'triangle' | 'hexagon';

function getArea2(shape: Shape2): number {
switch (shape) {
case 'circle':
return Math.PI * 10 * 10;
case 'square':
return 10 * 10;
case 'triangle':
return (10 * 10) / 2;
default:
// Error: Type '"hexagon"' is not assignable to type 'never'
// hexagonのケースが処理されていないことが分かる
const exhaustiveCheck: never = shape;
return exhaustiveCheck;
}
}

null と undefined

JavaScriptには「値がない」ことを表す2つの型があります。

nullとundefinedの違い

特徴nullundefined
意味意図的に値がないことを示す値が設定されていない
代入明示的に代入する初期化していない変数のデフォルト値
使用場面「空」であることを明示変数が初期化されていない
// undefinedの例
let value1: string | undefined;
console.log(value1); // undefined(初期化していない)

// nullの例
let value2: string | null = null; // 明示的に「空」を設定

strictNullChecksオプション

tsconfig.jsonstrictNullChecks: trueの場合、nullやundefinedの扱いが厳格になります。

// strictNullChecks: true の場合

// 各型にnullやundefinedは代入できない
let name: string = 'John';
// name = null; // Error
// name = undefined; // Error

// nullやundefinedを許可する場合はUnion型を使う
let name2: string | null = 'John';
name2 = null; // OK

let name3: string | undefined = 'John';
name3 = undefined; // OK

let name4: string | null | undefined = 'John';
name4 = null; // OK
name4 = undefined; // OK

安全なアクセス方法

// Optional Chaining(?.演算子)
let user: { name: string; email?: string } = {
name: 'John'
};

console.log(user.email?.toUpperCase()); // undefined(エラーにならない)

// Nullish Coalescing(??演算子)
let email = user.email ?? 'no-email@example.com';
console.log(email); // 'no-email@example.com'

実践的な使用例

// APIからのレスポンスなど、値がない可能性がある場合
function getUserEmail(): string | null {
// 実際にはAPIからデータを取得
return null; // メールが設定されていない場合
}

let userEmail: string | null = getUserEmail();

if (userEmail !== null) {
console.log(`Email: ${userEmail}`);
} else {
console.log('Email not found');
}

// Optional Chainingで安全にアクセス
console.log(userEmail?.toUpperCase() ?? 'N/A');

試してみよう: 安全な型ガードを実装しよう ★★

以下の要件を満たす関数を実装してください。

要件:

  1. processInput関数を作成
  2. 引数はunknown型で受け取る
  3. 引数がstring型の場合は大文字に変換して返す
  4. 引数がnumber型の場合は2倍にして返す
  5. 引数がboolean型の場合は反転して返す
  6. それ以外の場合はnullを返す
ヒント
  1. 引数の型をunknownとして定義する
  2. 戻り値の型をstring | number | boolean | nullとする
  3. typeof演算子で型をチェック
  4. 各型に応じた処理を実装
回答と解説
// unknown型を受け取り、型に応じた処理を行う関数
function processInput(value: unknown): string | number | boolean | null {
// string型の場合: 大文字に変換
if (typeof value === 'string') {
return value.toUpperCase();
}

// number型の場合: 2倍にする
if (typeof value === 'number') {
return value * 2;
}

// boolean型の場合: 反転する
if (typeof value === 'boolean') {
return !value;
}

// それ以外: nullを返す
return null;
}

// テスト
console.log(processInput('hello')); // 'HELLO'
console.log(processInput(21)); // 42
console.log(processInput(true)); // false
console.log(processInput([1, 2, 3])); // null
console.log(processInput({ a: 1 })); // null
console.log(processInput(undefined)); // null

解説:

  • unknown型を使うことで、どんな値でも受け取れますが、使用する前に型チェックが必要になります
  • typeof演算子を使って型を判定し、各型に応じた処理を行います
  • 型ガード(if (typeof value === 'string'))の中では、TypeScriptが自動的にvalueの型をstring型として認識します(型の絞り込み)
  • この方法はany型を使うよりも安全です。なぜなら、型チェックを行わないと操作できないからです

any型を使った場合との比較:

// any型を使った場合(危険)
function processInputAny(value: any): string | number | boolean | null {
// 型チェックなしで操作できてしまう
return value.toUpperCase(); // エラーにならないが、実行時エラーの可能性
}

// unknown型を使った場合(安全)
function processInputUnknown(value: unknown): string | number | boolean | null {
// return value.toUpperCase(); // Error: 型チェックが必要
if (typeof value === 'string') {
return value.toUpperCase(); // OK
}
return null;
}

まとめ

この章で学んだこと:

  • any型: すべての型チェックを無効化。使用は最小限に抑えるべき
  • unknown型: 型安全なany。使用前に型チェックが必要
  • void型: 戻り値がない関数に使用
  • never型: 決して値を返さない関数に使用。網羅性チェックにも活用
  • null/undefined: 値がないことを表す。strictNullChecksで厳格に管理

重要なポイント:

  • any型よりもunknown型を使おう
  • 型ガードを使って安全に型を絞り込もう
  • never型で網羅性チェックを行おう

次の章では、Union型・リテラル型・enumについて学びます。

初学者がつまずきやすいポイント

警告

よくある間違い

❌ anyとunknownの違いを理解していない

// ❌ 間違い: anyを使って型チェックを無視
function processAny(value: any): string {
return value.toUpperCase(); // 実行時エラーの可能性
}

processAny(123); // コンパイルは通るが実行時エラー

// ✅ 正しい: unknownを使って型チェックを強制
function processUnknown(value: unknown): string {
if (typeof value === 'string') {
return value.toUpperCase(); // 型チェック後なので安全
}
return 'Not a string';
}

原因: anyは型チェックを完全にスキップしますが、unknownは使用前に型チェックを要求します。

解決策: 型が不明な場合はanyではなくunknownを使用し、型ガードで絞り込んでください。

❌ voidとundefinedを混同する

// ❌ 間違い: void関数でundefinedを返そうとする
function logMessage(): void {
return undefined; // 動作するが、意図が不明確
}

// ✅ 正しい: voidは「戻り値を使用しない」という意図を表す
function logMessage(): void {
console.log('Hello');
// return文なし、または return; だけ
}

原因: voidは「戻り値がない」ことを示し、undefinedは「undefinedという値を返す」ことを示します。

解決策: 戻り値を使用しない関数はvoid、明示的にundefinedを返す必要がある場合はundefinedを使用してください。

❌ nullチェックを忘れる

// ❌ 間違い: nullの可能性を考慮していない
function getLength(value: string | null): number {
return value.length; // Error: value is possibly 'null'
}

// ✅ 正しい: nullチェックを行う
function getLength(value: string | null): number {
if (value === null) {
return 0;
}
return value.length;
}

// または Optional Chaining と Nullish Coalescing を使用
function getLength(value: string | null): number {
return value?.length ?? 0;
}

原因: strictNullChecksが有効な場合、nullの可能性がある値をそのまま使用できません。

解決策: nullチェック、Optional Chaining (?.)、Nullish Coalescing (??) を活用してください。

❌ neverの使いどころを理解していない

// ❌ 間違い: 新しいケースを追加したのに処理を忘れる
type Status = 'pending' | 'success' | 'error' | 'cancelled'; // cancelledを追加

function handleStatus(status: Status): string {
switch (status) {
case 'pending': return '処理中';
case 'success': return '成功';
case 'error': return 'エラー';
// cancelledの処理を忘れている!
}
}

// ✅ 正しい: neverで網羅性をチェック
function handleStatus(status: Status): string {
switch (status) {
case 'pending': return '処理中';
case 'success': return '成功';
case 'error': return 'エラー';
case 'cancelled': return 'キャンセル済み';
default:
const exhaustiveCheck: never = status;
throw new Error(`Unhandled status: ${exhaustiveCheck}`);
}
}

原因: never型を使った網羅性チェックを行わないと、新しいケースの追加時に処理漏れが発生します。

解決策: switch文のdefaultでnever型へ代入し、コンパイル時に漏れを検出してください。

❌ 暗黙的anyを見逃す

// ❌ 間違い: 型注釈なしで変数を宣言
let data; // 暗黙的any
data = 'Hello';
data = 123; // 型エラーにならない

// ✅ 正しい: 型注釈を明示する
let data: string;
data = 'Hello';
// data = 123; // Error: Type 'number' is not assignable to type 'string'

原因: 初期値なしで型注釈もない変数は暗黙的にany型になります。

解決策: tsconfig.jsonnoImplicitAny: trueを設定し、暗黙的anyを禁止してください。


次に読む