TypeScript基礎|型の基礎・知識をざっくりまとめ
TypeScriptの型の基本をざっくりとまとめる。
5/26/2025
TypeScriptとは?
TypeScriptは、JavaScriptに静的型付けの仕組みを加えた上位互換の言語。
書いたコードはJavaScriptにトランスパイルされ、あらゆる環境で実行できる。
プロジェクトが大規模になっても、保守・運用が破綻せずに成長を続けられる状態(スケーラブル)で開発が可能。
TypeScriptはこの「スケーラビリティ」に非常に強い。
📝基本的な型と静的型付け
number,string,boolean,null,undefined,any,unknown- 関数の型注釈(引数・戻り値)
- 静的型付けのメリット(型安全、バグ防止)
function sum(a: number, b: number): number {
return a + b;
}a: number, b: number → どちらも数値型
: number → 戻り値の型指定
型エイリアス(type)
型に名前をつけるための構文。オブジェクト型だけでなく、ユニオン型などあらゆる型を定義できる。
type User = {
name: string;
age: number;
};インデックスアクセス型(indexed access types)
オブジェクト型の中から、特定のプロパティの型を取り出すための書き方。
type User = {
name: string;
age: number;
};
type NameType = User["name"]; // string
type AgeType = User["age"]; // numberUser["name"] で、User 型の中の "name" の型(string)を取り出している。これが インデックスアクセス型。
🔸ユニオンで複数のキーを指定する
type User = {
name: string;
age: number;
active: boolean;
};
type NameOrAge = User["name" | "age"];
// → string | number"name" | "age" という複数のキーを指定すると、それぞれの型をまとめたユニオン型になる。
🔸keyofと組み合わせる
type User = {
name: string;
age: number;
};
type UserKeys = keyof User; // "name" | "age"
type AllValues = User[UserKeys]; // string | numberkeyof User > "name" | "age"。
User[UserKeys] は User["name"] | User["age"] と同じ > string | number。
ジェネリクス (Generics)
「あとで決める型」
関数やクラス、型に対して「汎用的な型」を与え、使うときに具体的な型を指定できるようにする。
ジェネリクスを使うと、柔軟さと型安全性の両立ができる。
🔸 例:普通の関数(型を固定)
これは「文字列しか受け取れない」関数。他の型では使えない。
function echoString(arg: string): string {
return arg;
}🔸 例:型をanyすると柔軟に受け取れる
function echoAny(arg: any): any {
return arg;
}🌀けれど・・・anyは柔軟だけど、型の安全性が失われる。
下記のコードでは、通常数値に対してtoUpperCase()は実行するとエラーになるが、下記のように間違った使い方をしてもコンパイルエラーにならない。
const result = echoAny(123); // 結果は any 型。型補完が効かない
result.toUpperCase(); // 型チェックされていない🔸 Generics を使った安全な関数
function echo<T>(arg: T): T {
return arg;
}T は 「あとで決める型」。実際に呼び出すときに TypeScript が型を推論してくれる。
const result1 = echo("hello"); // T は stringと推論される
const result2 = echo(123); // T は numberと推論されるそして型が保持される✅
const result = echo(123); // T は number
result.toFixed(); // OK
result.toUpperCase(); // エラー上記は、変数resultに関数echoの戻り値を渡している。関数echoには引数にnumber型の123を渡す。
変数resultに入るのはnumberと推論される。
そのため、resultにtoUpperCase()を適応しようとすると、toUpperCase()はstring にしか存在しないメソッドなので、number 型ではコンパイルエラーになる。
ユニオン型(union type)
複数の型のうち、どれか一つをとる という型。
「この変数はA型またはB型になる可能性がある」と表現したいときに使う。
|(パイプ)を使って書く。
例:初期値がnullの変数を処理する場合、ユニオン型を使うことが可能。
let value: string | number;
value = "Hello"; // OK(string)
value = 123; // OK(number)
value = true; // エラー(booleanは許可されていない)タプル型 (Tuple)
複数の値を決まった順番・型でまとめて扱う型。配列に似ているけど、それぞれの要素の型と順番が固定されている。
そのため、配列より厳密に「型の順番」を決めることができ、関数の戻り値や複数の値を返すときに便利。
例:[string, number] は「1番目が文字列、2番目が数字」という意味。
const user: [string, number] = ["Yamada", 28];user[0]は必ず文字列(名前)user[1]は必ず数字(年齢)
Mapped Types(マップ型)
既存の型を使って新しい型を動的に生成する。
type MyMappedType<T> = {
[P in keyof T]: T[P];
};T:元になる型(例: オブジェクトの型)
P in keyof T:Tのキーすべてを1つずつ取り出して、それに対して処理を行う。(例: "name" | "age")
T[P]:そのキーの型
🔸 例1|基本
type User = {
name: string;
age: number;
};
type CopiedUser = {
[K in keyof User]: User[K];
};
// 結果:
// type CopiedUser = {
// name: string;
// age: number;
// }User 型はプロパティ name(string)と age(number)を持つことを意味している。
CopiedUserのkeyof Userでは、User 型のキー(プロパティ名)だけを取り出したユニオン型を返す。(keyof User // → "name" | "age")。
[K in keyof User]: User[K] の部分がMapped Typeの本体。
[K in keyof User]: K を "name" と "age" の2つでループする。User[K]: K が "name" のときは string、"age" のときは number。
自動的に次のような型が生成される。
type CopiedUser = {
name: string; // ← K = "name", User[K] = User["name"] = string
age: number; // ← K = "age", User[K] = User["age"] = number
}= 元のUser型を丸ごとコピーしている。
上記の書き方で、値の型を変えたり(User[K] → boolean など)、オプショナルにしたり([K in keyof T]?: ...)、readonly を付けたり、柔軟に型を加工することができる。
条件型(Conditional Types)
条件型(Conditional Types)は、型に対して「もし〇〇なら、⬜︎⬜︎する、そうでなければ△△」という条件分岐を行う。
JavaScriptの三項演算子(条件 ? 真 : 偽)に似た構文で書く。
T extends U ? X : YT extends U: > T が U に代入できる(互換性がある)なら…
? X: > そのときの型は X
: Y: >そうでなければ Y
🔸 例:条件型 の基本
type Response<T> = T extends Error ? "ErrorType" : "SuccessType";
type R1 = Response<Error>; // "ErrorType"
type R2 = Response<string>; // "SuccessType"T が Error 型であれば "ErrorType"、そうでなければ "SuccessType"となる。
T extends Error > 条件式(T は Error 型に代入可能か?)
? "ErrorType" > 条件が true の場合の型
: "SuccessType" > 条件が false の場合の型
🔸 分配的条件型(Distributive Conditional Types)
条件型をユニオン型に使うと、各要素に分解されて評価される(分配的条件型)という特徴がある。
type Check<T> = T extends string ? "String" : "Other";
type A = Check<"hello" | 123>;
// 実際の型 → "String" | "Other"上記のように、TypeScriptは条件型を各ユニオンメンバーに対して個別に適用し、結果を再びユニオン型としてまとめる。
🔸 条件型の応用例|Exclude 型(ある型を除外する)
下記コードの目的は、T から U に含まれる型だけを取り除くこと。
つまり、「Tの中でUと一致するものを除外」する。→ (ユーティリティ型のExclude)
type MyExclude<T, U> = T extends U ? never : T;
type A = MyExclude<"a" | "b" | "c", "a" | "c">; // "b" Tの中の"a" は Uの中の"a" | "c" に含まれる > 除外 > never
Tの中の"b" はUに含まれない > 残る
Tの中の"c" はUに含まれる > 除外 > never
最終的に残るのは"b"のみとなる。
keyof演算子
オブジェクト型のプロパティ名をユニオン型として取り出すために使われる。
type Person = {
name: string;
age: number;
};
type PersonKeys = keyof Person; // "name" | "age"keyof Person はPerson型 のキー(プロパティ名)を "name" | "age" のようなユニオン型として返す。
🔸 よくある用途|ジェネリック+keyof の組み合わせ
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const p = { name: "Yamada", age: 30 };
getProperty(p, "name"); // OK, "Yamada"
getProperty(p, "age"); // OK, 30
getProperty(p, "email"); // エラー(存在しないプロパティ)getProperty(p, "name")を実行すると、getProperty<T, K extends keyof T>の T は { name: string; age: number } に推論される。
K extends keyof T で「Tのキー(プロパティ名)のどれかだけ許可」という制約をかける。
この場合、K は "name" (string型)または "age"(number型) のどれかに限る。K はこのどれか1つしか取れない。
(obj: T, key: K)の部分は型注釈。objは T 型。プロパティを持つオブジェクトの型。(nameというstringのプロパティと、ageというnumberのプロパティを持つオブジェクト。)key: K の部分は渡された "name"(プロパティ名)となる。
return obj[key]; =obj: T, key: k = p["name"] > "Yamada"、型は T[K] = string となる。
🔸 よくある用途|Mapped Typesの組み合わせ
type ReadOnly<T> = {
readonly [K in keyof T]: T[K];
};
type Person = { name: string; age: number };
type ReadonlyPerson = ReadOnly<Person>;ReadOnly<T> はT にオブジェクト型を渡すと、それを読み取り専用に変換する。
[K in keyof T]は渡されたオブジェクトの キー( "name" や "age")を K という名前でループして処理する。
readonly [K in keyof T]: T[K] > 各プロパティ K に対して、型 T[K](元の型)を使って、それに readonly 修飾子を付ける。
結果
type ReadonlyPerson = {
readonly name: string;
readonly age: number;
};typeof型演算子
ある変数や値から、その“型”を取り出すために使う。
const user = {
name: "Yamada",
age: 30
};
type User = typeof user;user はオブジェクトで、型は { name: string; age: number }。typeof user によって その型情報を取得して User に代入できる。
結果
type User = {
name: string;
age: number;
};🔸 よくある用途|変数から型を抽出
const config = {
host: "localhost",
port: 8080
};
type Config = typeof config;
// Config の型は { host: string; port: number }💡ReturnType などのユーティリティ型と一緒に使うことが多い。
クラス(class)
実際のオブジェクトを作る「設計図+処理」の両方を持つもの。
具体的な役割
- オブジェクトの状態(プロパティ)を持つ
- オブジェクトの動作(メソッド)を持つ
- コンストラクタで初期化ができる
- 何度も似た構造のオブジェクト(インスタンス)を作れる
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
greet() {
console.log(`Hello, my name is ${this.name}`);
}
}
const p = new Person("Yamada");
p.greet(); // Hello, my name is Yamadaクラスはそのままでは使用できない。new+Class名でインスタンス化して使用する。
constructorはクラスからオブジェクト(インスタンス)を作るときに最初に呼ばれる特別なメソッド。
インスタンスの初期化処理を書く場所で、例えばプロパティに値をセットしたり、初期状態を設定したりできる。
new Person("Yamada")でインスタンスを作るときに、constructor に渡した "Yamada" が引数として入り、this.nameに代入されてインスタンスの状態が初期化される。
インターフェース(interface)
主にオブジェクトの構造を定義するために使う。「型の契約」。
どんなプロパティやメソッドを持つかだけ決めて、具体的な処理は書きません。
interface Person {
name: string;
age: number;
greet(): void;
}implements(インプリメンツ)
クラスが interfaceの設計に従うことを明示的に宣言するキーワード。
クラスはそのinterfaceで決められたプロパティやメソッドを必ず実装しなければならない。
interface Person {
name: string;
age: number;
greet(): void;
}
class User implements Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, I'm ${this.name}`);
}
}User クラスは Person インターフェースの形に従うので、name と age プロパティ、greet メソッドを必ず持つ。
もし何かが足りないとコンパイルエラーになる。
上記を使用するには…
// インスタンス化
const user = new User("Yamada",30);
console.log(user.age); // 30
user.greet(); // Hello, I'm Yamadanew User("Yamada",30) で User クラスのインスタンスを作成。constructor の引数 "Yamada" が name プロパティに、30がageプロパティにセットされる。
user は User 型のオブジェクトとなり、greet メソッドを使える。