04. 型の基本

プリミティブ・配列・オブジェクト・union / typeinterface の使い分け

📅 作成: 2026-08-29 / 更新: 2026-08-29

この章の到達点

この章の内容

  1. プリミティブと配列
  2. オブジェクトの型
  3. union 型とリテラル型
  4. type と interface
  5. null と undefined

04.1 プリミティブと配列

書き方は「変数名のうしろにコロン」

// JavaScript
let name = "Alice";
let age = 30;

// TypeScript
let name: string = "Alice";
let age: number = 30;
値の例Java / C# との違い
string"Alice"`Hello, ${name}`char はない。1文字も string
number303.14-1intdouble の区別がない。すべて64ビット浮動小数点
booleantruefalse同じ
bigint10nJava の BigInteger に近い。リテラルに n が付く
symbolSymbol("id")相当するものがない。一意なキーを作る

型を書かなくても型は付きます。

let name = "Alice"; と書けば、TypeScript は namestring推論します。冗長に書く必要はありません。書くべきなのは、推論に任せると意図と違う型になる場所です。

配列

const names: string[] = ["Alice", "Bob"];
const ages: Array<number> = [30, 25];      // 同じ意味。string[] のほうが一般的

const pairs: [string, number] = ["Alice", 30];   // タプル(要素数と型が固定)

Java・C# から来た人へ

TypeScript の配列は長さが可変で、Java の ArrayList、C# の List<T> に相当します。string[] という記法は Java・C# の配列と同じ見た目ですが、固定長ではありません。

要素数を固定したいときはタプルを使います。[string, number] は「1つ目が string、2つ目が number の2要素」を意味します。

読み取り専用にする

const names: readonly string[] = ["Alice", "Bob"];
names.push("Carol");
//    ~~~~
// error TS2339: Property 'push' does not exist on type 'readonly string[]'.
//
// 訳: プロパティ 'push' は型 'readonly string[]' に存在しません。

const再代入を防ぐだけで、中身の変更は防ぎません。中身を変えられたくないときは readonly を使います。

04.2 オブジェクトの型

形をそのまま書く

// JavaScript
const user = { name: "Alice", age: 30 };

// TypeScript — 型を別に定義する
type User = {
	name: string;
	age: number;
};

const user: User = { name: "Alice", age: 30 };

省略可能なプロパティ

type User = {
	name: string;
	age?: number;        // あってもなくてもよい
};

const a: User = { name: "Alice" };              // OK
const b: User = { name: "Bob", age: 25 };       // OK

age?: numberage: number | undefined とほぼ同じですが、? は「キー自体がなくてもよい」点が違います。

書き方キーを省略undefined を代入
age?: numberできるできる
age: number | undefinedできないできる

readonly なプロパティ

type Config = {
	readonly host: string;
	readonly port: number;
};

const c: Config = { host: "localhost", port: 3000 };
c.port = 8080;
// ~~~~~~
// error TS2540: Cannot assign to 'port' because it is a read-only property.
//
// 訳: 'port' は読み取り専用プロパティのため、代入できません。

インデックスシグネチャ

キーが決まっていないオブジェクトを表します。

type Counts = { [key: string]: number };

const c: Counts = { apple: 3, banana: 5 };
c.orange = 2;                     // 任意のキーを足せる

インデックスシグネチャは検査を弱めます。

どんなキーでも許されるため、打ち間違いを検出できません。キーが決まっているなら列挙し、決まっていないなら Map を使うか、Record<string, number>A4)を検討してください。

04.3 union 型とリテラル型

TypeScript で最もよく使う道具

union 型は「いずれか」を表します。縦棒で並べます。

let id: string | number;
id = "abc";     // OK
id = 123;       // OK
id = true;      // エラー TS2322

リテラル型 — 「この値だけ」

型の位置に値そのものを書けます。union と組み合わせると強力です。

type Status = "pending" | "active" | "closed";

let s: Status = "active";      // OK
s = "activ";                   // エラー(打ち間違いを検出できる)
//  ~
// error TS2322: Type '"activ"' is not assignable to type 'Status'.
//
// 訳: 型 '"activ"' を型 'Status' に割り当てることはできません。

Java・C# から来た人へ — これが enum の代わりです

Java の enum、C# の enum に相当する用途は、TypeScript では文字列リテラルの union で書くのが主流です。

// TypeScript にも enum はあるが、使わないことが多い
enum Status { Pending, Active, Closed }

// こちらが推奨
type Status = "pending" | "active" | "closed";

理由は3つあります。①実行時のコードを生成しない(型だけで完結する)、②値がそのまま文字列なのでログや JSON でそのまま読めるNode の型ストリップenum は使えない

as const — リテラル型として固定する

let s1 = "active";                    // 型は string(後で別の文字列を入れられる)
const s2 = "active";                  // 型は "active"(const なので変わらない)

const arr1 = ["a", "b"];              // 型は string[]
const arr2 = ["a", "b"] as const;     // 型は readonly ["a", "b"]

as const「この値のまま固定する」指示です。設定オブジェクトを定義するときによく使います。

const CONFIG = {
	host: "localhost",
	port: 3000,
} as const;

// CONFIG.host の型は "localhost"(string ではない)
// CONFIG.port = 8080;   ← エラー。readonly になる

union は「絞り込んで」使う

union のままでは、共通して持つ操作しかできません。

function show(id: string | number) {
	console.log(id.toUpperCase());
	//             ~~~~~~~~~~~
	// error TS2339: Property 'toUpperCase' does not exist on type 'string | number'.
	//
	// 訳: プロパティ 'toUpperCase' は型 'string | number' に存在しません。
}
string | number 共通の操作しかできない typeof x === "string" string toUpperCase() が使える number toFixed() が使える true false この位置では toUpperCase() は呼べない
図 1 — union はそのままでは使えない。確かめると、その分岐の中では確定した型になる

どちらなのかを確かめてから使います。この操作を絞り込み(narrowing)と呼び、08. 型の絞り込みで詳しく扱います。

function show(id: string | number) {
	if (typeof id === "string") {
		console.log(id.toUpperCase());   // ここでは string として扱える
	} else {
		console.log(id.toFixed(2));      // ここでは number
	}
}

04.4 type と interface

どちらでもオブジェクトの形を書ける

type UserA = {
	name: string;
	age: number;
};

interface UserB {
	name: string;
	age: number;
}

この2つはほぼ同じです。相互に代入でき、どちらも構造的型付け(05)で判定されます。

違いは3つだけ

項目typeinterface
union を作れるできるできない
同名で再宣言エラー合体する
プリミティブに別名できるできない
// type にしかできないこと
type Status = "pending" | "active";     // union
type ID = string;                       // プリミティブの別名

// interface にしかできないこと — 同名の宣言が合体する(宣言のマージ)
interface Window { myApp: string; }     // 既存の Window に足す

迷ったら type を使ってください。

union やタプルを扱えるぶん type のほうが用途が広く、意図せず合体することもありません。interface が要るのは、外部ライブラリの型を拡張する(宣言のマージ)場合です。

ただしプロジェクト内で統一されているなら、そちらに合わせてください。混在しているほうが読みにくくなります。

Java・C# から来た人へ

TypeScript の interface は、Java・C# の interface とは別物です。

  • implements を書かなくても、形が合っていれば互換になります(05. 構造的型付け
  • 実行時には存在しません。x instanceof User と書けません(01. 背景
  • データの形を表すために使われ、「実装すべき契約」という意味合いは弱くなります

組み合わせる

type Base = { id: string };
type Timestamps = { createdAt: Date };

type Entity = Base & Timestamps;        // 交差型(両方の性質を持つ)

const e: Entity = { id: "1", createdAt: new Date() };

interface では extends を使います。

interface Entity extends Base, Timestamps {}

04.5 null と undefined

2つある

意味いつ現れるか
undefined「まだ入っていない」初期化していない変数、存在しないプロパティ、戻り値のない関数
null「意図的に空」明示的に代入したとき、一部の API の戻り値

実務では undefined に寄せるのが一般的です。

JavaScript の言語機能(省略可能引数、分割代入の既定値、?.)が undefined を前提にしているためです。null は外部から来たとき(JSON・DB・API)だけ扱うと決めておくと、判定が減ります。

strictNullChecks があると代入できない

strict 03. tsconfig で見たとおり、strict が有効だと null を勝手に入れられません。

let s: string = null;
//  ~
// error TS2322: Type 'null' is not assignable to type 'string'.
//
// 訳: 型 'null' を型 'string' に割り当てることはできません。

let t: string | null = null;      // これなら通る

安全に扱う3つの記法

type User = { name: string; profile?: { bio: string } };

function show(u: User) {
	// ① オプショナルチェーン — undefined なら undefined を返す
	console.log(u.profile?.bio);

	// ② null 合体演算子 — null / undefined のときだけ既定値
	console.log(u.profile?.bio ?? "(未設定)");

	// ③ 絞り込み — if で確かめる
	if (u.profile) {
		console.log(u.profile.bio);      // ここでは確実にある
	}
}

??|| は違います。

||0"" も「偽」として既定値に置き換えてしまいます。??nullundefined のときだけ働きます。

const port1 = 0 || 3000;     // 3000 になる(0 が消える)
const port2 = 0 ?? 3000;     // 0 のまま

設定値を扱うときは ?? を使ってください。「0 を指定したのに既定値になる」という不具合の原因になります。

動かしてみる

// null-check.ts
type User = { name: string; age?: number };

function describe(u: User): string {
	return `${u.name} (${u.age ?? "年齢不明"})`;
}

console.log(describe({ name: "Alice", age: 30 }));
console.log(describe({ name: "Bob" }));
$ npx tsx null-check.ts
Alice (30)
Bob (年齢不明)