03. tsconfig 05. 構造的型付け 資料トップへ戻る
type と interface のどちらを使うか判断できる// 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 |
number | 30、3.14、-1 | int と double の区別がない。すべて64ビット浮動小数点 |
boolean | true、false | 同じ |
bigint | 10n | Java の BigInteger に近い。リテラルに n が付く |
symbol | Symbol("id") | 相当するものがない。一意なキーを作る |
型を書かなくても型は付きます。
let name = "Alice"; と書けば、TypeScript は name を string と推論します。冗長に書く必要はありません。書くべきなのは、推論に任せると意図と違う型になる場所です。
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 を使います。
// 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?: number は age: number | undefined とほぼ同じですが、? は「キー自体がなくてもよい」点が違います。
| 書き方 | キーを省略 | undefined を代入 |
|---|---|---|
age?: number | できる | できる |
age: number | undefined | できない | できる |
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)を検討してください。
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 は使えない。
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 のままでは、共通して持つ操作しかできません。
function show(id: string | number) {
console.log(id.toUpperCase());
// ~~~~~~~~~~~
// error TS2339: Property 'toUpperCase' does not exist on type 'string | number'.
//
// 訳: プロパティ 'toUpperCase' は型 'string | number' に存在しません。
}
どちらなのかを確かめてから使います。この操作を絞り込み(narrowing)と呼び、08. 型の絞り込みで詳しく扱います。
function show(id: string | number) {
if (typeof id === "string") {
console.log(id.toUpperCase()); // ここでは string として扱える
} else {
console.log(id.toFixed(2)); // ここでは number
}
}
type UserA = {
name: string;
age: number;
};
interface UserB {
name: string;
age: number;
}
この2つはほぼ同じです。相互に代入でき、どちらも構造的型付け(05)で判定されます。
| 項目 | type | interface |
|---|---|---|
| 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 {}
| 値 | 意味 | いつ現れるか |
|---|---|---|
undefined | 「まだ入っていない」 | 初期化していない変数、存在しないプロパティ、戻り値のない関数 |
null | 「意図的に空」 | 明示的に代入したとき、一部の API の戻り値 |
実務では undefined に寄せるのが一般的です。
JavaScript の言語機能(省略可能引数、分割代入の既定値、?.)が undefined を前提にしているためです。null は外部から来たとき(JSON・DB・API)だけ扱うと決めておくと、判定が減ります。
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; // これなら通る
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 や "" も「偽」として既定値に置き換えてしまいます。?? は null と undefined のときだけ働きます。
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 (年齢不明)
まとめ
| 項目 | この章の結論 |
|---|---|
| 基本型 | number に int/double の区別はない。readonly で中身を守る |
| オブジェクト | ? は「キーがなくてもよい」。インデックスシグネチャは検査を弱める |
| union・リテラル | TypeScript で最もよく使う道具。enum の代わりに文字列リテラルの union を使う |
type/interface | ほぼ同じ。迷ったら type。interface は宣言のマージが要るときだけ |
null/undefined | undefined に寄せる。?? と || を混同しない |
次は 05. 構造的型付け です。この資料の山場のひとつで、Java・C# 経験者が最もつまずく箇所です。