- Day 18:型の絞り込み(Type Narrowing)で「安全に分岐する」感覚を身につける
- 型の絞り込みとは:ざっくり「今はこの型だ」と確信していくこと
- typeofでプリミティブ型を絞り込む
- printValue関数を少しアレンジしてみる
- instanceofでオブジェクト型を絞り込む
- inでプロパティの有無から型を絞り込む
- Union Typeとの組み合わせで「賢い分岐」を書く
- Day 18 練習テンプレート:型の絞り込みをまとめて体験する
- Day 18のまとめ
- Day 18:型の絞り込み演習で「安全な判定ロジック」を身につける
- String / Number判定:typeofでプリミティブ型を絞り込む
- User / Admin判定:instanceofとUnionで「役割」を絞り込む
- 商品 / ユーザー判定:inでプロパティの有無から絞り込む
- Day 18 演習テンプレート:3つの判定をまとめて練習する
- Day 18のまとめ
Day 18:型の絞り込み(Type Narrowing)で「安全に分岐する」感覚を身につける
第3週は、TypeScriptの核心である「型」をじっくり育てていく週です。 Day 18のテーマは 型の絞り込み(Type Narrowing)。 Union Typeと組み合わせて、「状況に応じて型を狭くしていく」考え方を身につけていきます。
学習内容は次のとおりです。
typeofinstanceofin- Type Narrowing
- Unionとの組み合わせ
実践として、文字列と数値の両方を処理する関数を題材に、型の絞り込みを体験していきます。
型の絞り込みとは:ざっくり「今はこの型だ」と確信していくこと
Union Typeのままだと「どっちかわからない」
まず、次のようなUnion Typeを考えます。
// 文字列か数値のどちらか
type StringOrNumber = string | number;
TypeScriptこの型を引数に持つ関数を作ると、こうなります。
function printValue(value: string | number) {
// ここでは value が string か number か、まだわからない
}
TypeScriptvalue の型は string | number なので、 TypeScriptから見て「文字列か数値か、どちらの可能性もある」という状態です。
このままでは、value.toUpperCase() も value.toFixed(2) も、 「どちらかの型にしか存在しないメソッド」なので、素直には呼べません。
ここで必要になるのが 型の絞り込み(Type Narrowing)です。
typeofでプリミティブ型を絞り込む
実践例:printValue関数
まずは、提示されている関数をじっくり見てみます。
function printValue(value: string | number) {
if (typeof value === "string") {
console.log(value.toUpperCase());
} else {
console.log(value.toFixed(2));
}
}
TypeScriptこの関数は、次のような動きをします。
- 引数
valueはstring | number(文字列か数値のどちらか) typeof value === "string"のときは、文字列として扱う- それ以外(ここでは
number)のときは、数値として扱う
型の絞り込みがどう効いているか
if (typeof value === "string") のブロックの中では、 TypeScriptは「この中では value は string だ」と理解します。
if (typeof value === "string") {
// ここでは value は string 型に絞り込まれている
console.log(value.toUpperCase()); // stringのメソッドが使える
} else {
// ここでは value は number 型に絞り込まれている
console.log(value.toFixed(2)); // numberのメソッドが使える
}
TypeScript重要ポイント(ここはしっかり深掘りしたいところです):
- Union Type(
string | number)のままだと、「どちらのメソッドも安全に呼べない」状態です。 typeofを使って条件分岐することで、「このブロックの中ではstring」「このブロックの中ではnumber」と、型を狭くできます。- これが Type Narrowing(型の絞り込み)であり、TypeScriptが「文脈に応じて型を理解してくれる」仕組みです。
printValue関数を少しアレンジしてみる
実行例を追加する
実際にどう動くか、テストコードを足してみます。
function printValue(value: string | number) {
if (typeof value === "string") {
// ここでは value は string 型
console.log("文字列として処理:", value.toUpperCase());
} else {
// ここでは value は number 型
console.log("数値として処理:", value.toFixed(2));
}
}
// テスト
printValue("hello"); // 文字列として処理: HELLO
printValue(3.14159); // 数値として処理: 3.14
TypeScriptコメントを付けることで、「どのタイミングで型が絞り込まれているか」が視覚的にもわかりやすくなります。
instanceofでオブジェクト型を絞り込む
クラスを使った例
typeof は主にプリミティブ型(string, number, boolean など)に使います。 オブジェクトやクラスのインスタンスを絞り込みたいときは、instanceof を使います。
class Person {
constructor(public name: string) {}
}
class Animal {
constructor(public species: string) {}
}
function printEntity(entity: Person | Animal) {
if (entity instanceof Person) {
// ここでは entity は Person 型
console.log("人の名前:", entity.name);
} else {
// ここでは entity は Animal 型
console.log("動物の種類:", entity.species);
}
}
TypeScriptポイント:
entity instanceof Personのブロックでは、entityがPerson型に絞り込まれます。- それ以外のブロックでは、
Animal型に絞り込まれます。 - クラスベースのオブジェクトを扱うときに、
instanceofはよく使われます。
inでプロパティの有無から型を絞り込む
プロパティの存在チェックで絞り込む
in 演算子は、「オブジェクトが特定のプロパティを持っているかどうか」を調べるものです。 これを使って、Union Typeの中から「このプロパティを持つ型」を絞り込むことができます。
type User = {
name: string;
email: string;
};
type Guest = {
name: string;
};
// UserかGuestのどちらか
type UserOrGuest = User | Guest;
function printUserInfo(person: UserOrGuest) {
if ("email" in person) {
// ここでは person は User 型に絞り込まれている
console.log("ユーザーのメール:", person.email);
} else {
// ここでは person は Guest 型に絞り込まれている
console.log("ゲストユーザー:", person.name);
}
}
TypeScript重要ポイント:
if ("email" in person)のブロックでは、「emailプロパティを持つ型」だけに絞り込まれます。- Union Typeのメンバーが「持っているプロパティの違い」で区別できるときに、とても便利なパターンです。
Union Typeとの組み合わせで「賢い分岐」を書く
文字列と数値の両方を処理する関数をもう一歩発展させる
先ほどの printValue 関数を、少しだけ拡張してみます。 例えば、「数値のときは丸め方を変えたい」といったオプションを追加してみます。
// 丸め方のオプション
type RoundMode = "ceil" | "floor" | "round";
// 文字列と数値の両方を処理する関数(オプション付き)
function printValueAdvanced(value: string | number, mode: RoundMode = "round") {
if (typeof value === "string") {
// 文字列の場合は大文字にして表示
console.log("文字列:", value.toUpperCase());
} else {
// 数値の場合は mode に応じて丸め方を変える
let processed: number;
switch (mode) {
case "ceil":
processed = Math.ceil(value);
break;
case "floor":
processed = Math.floor(value);
break;
case "round":
default:
processed = Math.round(value);
break;
}
console.log("数値:", processed.toFixed(2));
}
}
// テスト
printValueAdvanced("hello"); // 文字列: HELLO
printValueAdvanced(3.14159); // 数値: 3.00(round)
printValueAdvanced(3.14159, "ceil"); // 数値: 4.00
printValueAdvanced(3.14159, "floor"); // 数値: 3.00
TypeScriptここでも、
valueはstring | numberのUnion Typemodeは"ceil" | "floor" | "round"のUnion Type(Literal Typeの組み合わせ)typeofとswitchを使って、それぞれの型に応じた処理を安全に書いています。
Day 18 練習テンプレート:型の絞り込みをまとめて体験する
最後に、今日の内容をひとつのファイルにまとめた練習テンプレートを載せます。 このままコピーして、条件分岐を増やしたり、型を変えたりしながら、自分なりの「型の絞り込み」を試してみてください。
// day18-type-narrowing.ts
// Day 18:型の絞り込み 練習テンプレート
// 1. typeof を使った文字列と数値の絞り込み
function printValue(value: string | number) {
if (typeof value === "string") {
// ここでは value は string 型
console.log("文字列として処理:", value.toUpperCase());
} else {
// ここでは value は number 型
console.log("数値として処理:", value.toFixed(2));
}
}
// 2. instanceof を使ったクラスインスタンスの絞り込み
class Person {
constructor(public name: string) {}
}
class Animal {
constructor(public species: string) {}
}
function printEntity(entity: Person | Animal) {
if (entity instanceof Person) {
console.log("人の名前:", entity.name);
} else {
console.log("動物の種類:", entity.species);
}
}
// 3. in を使ったプロパティによる絞り込み
type User = {
name: string;
email: string;
};
type Guest = {
name: string;
};
type UserOrGuest = User | Guest;
function printUserInfo(person: UserOrGuest) {
if ("email" in person) {
console.log("ユーザーのメール:", person.email);
} else {
console.log("ゲストユーザー:", person.name);
}
}
// 4. Union Type + Narrowing を組み合わせた発展例
type RoundMode = "ceil" | "floor" | "round";
function printValueAdvanced(value: string | number, mode: RoundMode = "round") {
if (typeof value === "string") {
console.log("文字列:", value.toUpperCase());
} else {
let processed: number;
switch (mode) {
case "ceil":
processed = Math.ceil(value);
break;
case "floor":
processed = Math.floor(value);
break;
case "round":
default:
processed = Math.round(value);
break;
}
console.log("数値:", processed.toFixed(2));
}
}
// テスト
printValue("hello");
printValue(3.14159);
printEntity(new Person("Taro"));
printEntity(new Animal("Cat"));
printUserInfo({ name: "Guest", email: "[email protected]" });
printUserInfo({ name: "Anonymous" });
printValueAdvanced("typescript");
printValueAdvanced(3.14159);
printValueAdvanced(3.14159, "ceil");
printValueAdvanced(3.14159, "floor");
TypeScriptDay 18のまとめ
Day 18では、
- Union Type(
string | numberなど)をそのまま使うと「どっちかわからない」状態になること typeof/instanceof/inを使って、文脈に応じて型を狭くしていく Type Narrowing の考え方- 文字列と数値の両方を処理する
printValue関数を通して、「絞り込み後の型に応じたメソッドを安全に呼ぶ」流れ - クラスインスタンスやプロパティの有無による絞り込みなど、Union Typeとの組み合わせパターン
を、具体的なコードとともに体験しました。
型の絞り込みは、TypeScriptが「コードの流れを理解してくれる」仕組みの中心的な機能です。 ぜひ、条件分岐を増やしたり、Union Typeのパターンを変えたりしながら、「この場面ではこの型だ」と確信していく感覚を育ててみてください。そうすると、型が味方になってくれて、安心して複雑なロジックを書けるようになっていきます。
Day 18:型の絞り込み演習で「安全な判定ロジック」を身につける
第3週は、TypeScriptの核心である「型」をじっくり育てていく週です。 Day 18の演習テーマは、型の絞り込み(Type Narrowing)を使った3つの判定ロジックです。
- String / Number判定(
typeof) - User / Admin判定(
instanceofや Unionとの組み合わせ) - 商品 / ユーザー判定(
in)
学習内容は次のとおりです。
typeofinstanceofin- Type Narrowing
- Unionとの組み合わせ
ここからは、それぞれをステップバイステップで見ていきます。
String / Number判定:typeofでプリミティブ型を絞り込む
なぜ判定が必要になるのか
まずは、「文字列か数値か、どちらかが来る」関数を考えます。
function printValue(value: string | number) {
// ここでは value が string か number か、まだわからない
}
TypeScriptvalue の型は string | number なので、TypeScriptから見て「文字列か数値か、どちらの可能性もある」という状態です。 このままでは、value.toUpperCase() も value.toFixed(2) も安全に呼べません。
そこで使うのが typeof による型の絞り込みです。
typeofでString / Numberを判定する
function printValue(value: string | number) {
if (typeof value === "string") {
// ここでは value は string 型に絞り込まれている
console.log("文字列として処理:", value.toUpperCase());
} else {
// ここでは value は number 型に絞り込まれている
console.log("数値として処理:", value.toFixed(2));
}
}
// テスト
printValue("hello"); // 文字列として処理: HELLO
printValue(3.14159); // 数値として処理: 3.14
TypeScript重要ポイント:
typeof value === "string"のブロックの中では、TypeScriptは「ここではvalueはstringだ」と理解します。- それ以外のブロックでは、「ここではvalueはnumberだ」と理解します。
- Union Type(
string | number)を「文脈に応じて狭くする」ことが、Type Narrowingの本質です。
User / Admin判定:instanceofとUnionで「役割」を絞り込む
クラスでUserとAdminを表現する
次に、UserとAdminをクラスで表現して判定するパターンを見ていきます。
class User {
constructor(public name: string) {}
}
class Admin {
constructor(public name: string, public permissions: string[]) {}
}
TypeScriptここで、「UserかAdminのどちらかが渡される」関数を作ります。
function printPersonInfo(person: User | Admin) {
if (person instanceof Admin) {
// ここでは person は Admin 型に絞り込まれている
console.log("管理者:", person.name);
console.log("権限一覧:", person.permissions.join(", "));
} else {
// ここでは person は User 型に絞り込まれている
console.log("一般ユーザー:", person.name);
}
}
// テスト
const user = new User("Taro");
const admin = new Admin("Admin Hanako", ["manage_users", "view_logs"]);
printPersonInfo(user);
printPersonInfo(admin);
TypeScript深掘りポイント:
person instanceof Adminのブロックでは、「Adminクラスのインスタンスだけ」に絞り込まれます。- それ以外のブロックでは、「Userクラスのインスタンス」に絞り込まれます。
- クラスベースのオブジェクトを扱うときに、
instanceofはとても自然な絞り込み方法です。
Union + Literal Typeで「role」を判定するパターン
クラスを使わずに、roleプロパティでUser / Adminを判定することもできます。
type Role = "user" | "admin";
type BaseUser = {
name: string;
role: Role;
};
type AdminUser = BaseUser & {
role: "admin";
permissions: string[];
};
type NormalUser = BaseUser & {
role: "user";
};
type AnyUser = AdminUser | NormalUser;
function printUserRoleInfo(user: AnyUser) {
if (user.role === "admin") {
// ここでは user は AdminUser 型に絞り込まれている
console.log("管理者ユーザー:", user.name);
console.log("権限:", user.permissions.join(", "));
} else {
// ここでは user は NormalUser 型に絞り込まれている
console.log("一般ユーザー:", user.name);
}
}
// テスト
const adminUser: AdminUser = {
name: "Admin Taro",
role: "admin",
permissions: ["manage_users", "view_logs"],
};
const normalUser: NormalUser = {
name: "User Hanako",
role: "user",
};
printUserRoleInfo(adminUser);
printUserRoleInfo(normalUser);
TypeScriptポイント:
user.role === "admin"のブロックでは、「roleがadminの型」に絞り込まれます。- Literal Type(
"admin" | "user")とUnion Typeを組み合わせることで、「ありえる役割」を型で表現できます。 - セキュリティ的にも、「存在しない権限」を扱わないようにするための重要なパターンです。
商品 / ユーザー判定:inでプロパティの有無から絞り込む
ProductとUserの型を用意する
最後に、商品とユーザーを同じ関数で扱い、どちらかを判定するパターンを見ていきます。
type Product = {
id: number;
name: string;
price: number;
};
type UserInfo = {
id: number;
name: string;
email: string;
};
// 商品かユーザーのどちらか
type ProductOrUser = Product | UserInfo;
TypeScriptinで「どのプロパティを持っているか」を見る
ここで、in 演算子を使って「emailプロパティの有無」で判定します。
function printEntityInfo(entity: ProductOrUser) {
if ("email" in entity) {
// ここでは entity は UserInfo 型に絞り込まれている
console.log("ユーザー情報:");
console.log("ID:", entity.id);
console.log("名前:", entity.name);
console.log("メール:", entity.email);
} else {
// ここでは entity は Product 型に絞り込まれている
console.log("商品情報:");
console.log("ID:", entity.id);
console.log("商品名:", entity.name);
console.log("価格:", entity.price);
}
}
// テスト
const product: Product = {
id: 100,
name: "ノートPC",
price: 120000,
};
const userInfo: UserInfo = {
id: 1,
name: "Taro",
email: "[email protected]",
};
printEntityInfo(product);
printEntityInfo(userInfo);
TypeScript重要ポイント:
if ("email" in entity)のブロックでは、「emailプロパティを持つ型」だけに絞り込まれます。- Union Typeのメンバーが「持っているプロパティの違い」で区別できるときに、とても便利なパターンです。
- 実際のアプリでも、「レスポンスがユーザーか商品かで形が違う」といった場面でよく使われます。
Day 18 演習テンプレート:3つの判定をまとめて練習する
最後に、今日の3つの判定パターンをひとつのファイルにまとめた練習テンプレートを載せます。 このままコピーして、条件分岐を増やしたり、型を変えたりしながら、自分なりの「型の絞り込み」を試してみてください。
// day18-narrowing-practice.ts
// Day 18:型の絞り込み 演習テンプレート
// 1. String / Number判定(typeof)
function printValue(value: string | number) {
if (typeof value === "string") {
console.log("文字列として処理:", value.toUpperCase());
} else {
console.log("数値として処理:", value.toFixed(2));
}
}
printValue("hello");
printValue(3.14159);
// 2. User / Admin判定(instanceof)
class User {
constructor(public name: string) {}
}
class Admin {
constructor(public name: string, public permissions: string[]) {}
}
function printPersonInfo(person: User | Admin) {
if (person instanceof Admin) {
console.log("管理者:", person.name);
console.log("権限一覧:", person.permissions.join(", "));
} else {
console.log("一般ユーザー:", person.name);
}
}
const user = new User("Taro");
const admin = new Admin("Admin Hanako", ["manage_users", "view_logs"]);
printPersonInfo(user);
printPersonInfo(admin);
// 2-2. User / Admin判定(roleによるUnion)
type Role = "user" | "admin";
type BaseUser = {
name: string;
role: Role;
};
type AdminUser = BaseUser & {
role: "admin";
permissions: string[];
};
type NormalUser = BaseUser & {
role: "user";
};
type AnyUser = AdminUser | NormalUser;
function printUserRoleInfo(user: AnyUser) {
if (user.role === "admin") {
console.log("管理者ユーザー:", user.name);
console.log("権限:", user.permissions.join(", "));
} else {
console.log("一般ユーザー:", user.name);
}
}
const adminUser: AdminUser = {
name: "Admin Taro",
role: "admin",
permissions: ["manage_users", "view_logs"],
};
const normalUser: NormalUser = {
name: "User Hanako",
role: "user",
};
printUserRoleInfo(adminUser);
printUserRoleInfo(normalUser);
// 3. 商品 / ユーザー判定(in)
type Product = {
id: number;
name: string;
price: number;
};
type UserInfo = {
id: number;
name: string;
email: string;
};
type ProductOrUser = Product | UserInfo;
function printEntityInfo(entity: ProductOrUser) {
if ("email" in entity) {
console.log("ユーザー情報:");
console.log("ID:", entity.id);
console.log("名前:", entity.name);
console.log("メール:", entity.email);
} else {
console.log("商品情報:");
console.log("ID:", entity.id);
console.log("商品名:", entity.name);
console.log("価格:", entity.price);
}
}
const productEntity: Product = {
id: 100,
name: "ノートPC",
price: 120000,
};
const userEntity: UserInfo = {
id: 1,
name: "Taro",
email: "[email protected]",
};
printEntityInfo(productEntity);
printEntityInfo(userEntity);
TypeScriptDay 18のまとめ
Day 18では、
typeofを使って、String / Numberを安全に判定する方法instanceofや Literal Type + Unionを使って、User / Adminを判定するパターンinを使って、商品 / ユーザーを「プロパティの違い」で判定する方法- それぞれの場面で、Type Narrowingによって「このブロックではこの型だ」と確信してコードを書く流れ
を、具体的なコードとともに体験しました。
型の絞り込みは、TypeScriptが「コードの流れを理解してくれる」仕組みの中心的な機能です。 ぜひ、条件分岐を増やしたり、Union Typeのパターンを変えたりしながら、「この場面ではこの型だ」と確信していく感覚を育ててみてください。そうすると、型が味方になってくれて、安心して複雑なロジックを書けるようになっていきます。
