これまで変数や定数を学んできましたが、プログラムが扱うすべてのデータには「型(Type)」というデータの種類が決まっています。
string (文字列): 名前やメッセージなど ("Shake")number (数値): 金額やカウントなど (123, 0.5)boolean (真偽値): はい/いいえの状態 (true, false)Array / Object: 複数のデータをまとめたもの「型」が必要な理由: 「名前(文字列)に 10 を足す」といった、人間から見たら明らかに 「意味の通らない操作」 を、コンピュータが事前(実行前)に見つけるための手がかりになります。
JavaScript は非常に自由ですが、その自由さがバグの原因になります。
TypeScript は JavaScript に 静的型付け を追加した言語です。
function buy(price) {
return price * 1.1;
}
buy("1,000円");
NaN(計算不能)となり、アプリの表示がおかしくなります。function safeBuy(price: number) {
return price * 1.1;
}
safeBuy("1,000円");
// ^^^^^^^^^
// Argument of type 'string' is not assignable to parameter of type 'number'.
price: number と 型(データの種類) を指定しました。const user = { name: "Shake", age: 20 };
// 下の行のコメントを外して、「user.」の後にタイピングしてみてください
// console.log(user.name);
型アノテーション(Type Annotation) とは、変数名や関数の引数の後ろに : 型名 を添えて、データの種類を明示的に指定することです。 TypeScript はこの情報をもとに、正しいデータが扱われているかをチェックします。
// 変数に型をつける
const userName: string = "Shake";
const age: number = 20;
const isStaff: boolean = true;
// 関数の引数と戻り値に型をつける
function add(a: number, b: number): number {
return a + b;
}
// アロー関数の場合
const greet = (name: string): string => {
return `Hello, ${name}`;
};
console.log(greet("Shake")); // "Hello, Shake"
TypeScriptでは変数の型を推論してくれますが、明示的に書くこともできます。 変数 count に「数値型」を明示的に指定するには、変数名の後ろに何と書けばよいですか?
// 数値型を明示する
const count____ = 10;
console.log(count);
// @test: typeof count !== 'undefined' && count === 10
const count: number = 10;
: number をつけることで、この変数は数値しか入れられないことを保証します。 (const なので再代入はできませんが、型としての意味は重要です)
次の JavaScript コードを TypeScript に書き換えて、引数と戻り値に型をつけてください。
function getLength(text) {
return text.length;
}
console.log(getLength("hello"));
// @test: typeof getLength("hello") === "number"
// @test: getLength("hello") === 5
// @test: getLength("") === 0
function getLength(text: string): number {
return text.length;
}
string 型と number 型を指定します。any 型を指定すると、その変数に対する TypeScript の型チェックがすべて無効化されます。
any は禁止」。どうしても型がわからない時の一時的な避難場所以外では使いません。let value: any = "Shake";
value = 123; // OK (型のエラーが出ない)
value.push(); // OK (?) ... 実際には数値に push はないので実行時にエラーになる!
どうしても型が分からない場合、現代のTypeScriptでは any ではなく unknown 型を使うのがベストプラクティスです。
unknown の特徴: 「何が入っているか分からない」という事実は同じですが、「中身を特定(型の絞り込み)するまでは、勝手な操作を許さない」 という安全装置が働きます。unknown で受け取り、後述するバリデーション(zodなど)を通して安全な型に変換してから使います。let safeValue: unknown = "Shake";
// safeValue.push(); // エラー!「よくわからないものに push はできません」と怒ってくれる
if (typeof safeValue === "string") {
// ここからは string として扱える(型の絞り込み)
console.log(safeValue.length);
}
どうしても型エラーが解決できない、あるいはライブラリ側の型定義が間違っている場合に、コンパイルエラーを無理やり消すコメントがあります。
// @ts-ignore (非推奨): 次の行で発生する型エラーを無条件で無視します。 // @ts-expect-error (推奨): 次の行で「エラーが出るはずだ」と宣言して黙らせます。 // @ts-ignore
const danger: number = "無視"; // エラーは消えるが、中身は文字列で危険
// @ts-expect-error
const better: number = "期待通り"; // エラーが解消されたら、このコメント自体がエラーになる
結論: どちらも「禁じ手」ですが、どうしても使うなら @ts-expect-error を選びましょう。
以下のコードで、TypeScript がエラー(赤線)を出すのはどの行でしょうか?
let price: any = 1000;
price = "無料";
function update(p: number) {
console.log(p);
}
update(price); // (A)
update("0"); // (B)
答え:(B)
price は any 型なので、(A) のように「数値が必要な関数」に渡してもエラーになりません(これが any の危険なところです)。オブジェクト(複数のデータがまとまったもの)が、「どのようなプロパティを持ち、それぞれの値が何型か」という「設計図(形)」 を定義します。
Object 型ではなく、具体的で名前のついた型(例: User)として扱えます。interface User {
id: string;
name: string;
email: string;
// プロパティ名の後ろに ? をつけると「あってもなくても良い(任意)」になります
age?: number;
}
// OK: 全ての必須項目が揃っている
const staff: User = {
id: "st-01",
name: "Taro",
email: "taro@example.com",
};
// NG: name が欠けているためエラーになる
const visitor: User = {
id: "v-02",
email: "visitor@example.com",
};
以下の条件を満たす Staff 型を interface で定義し、変数を作成してください。
id: 数値 (必須)name: 文字列 (必須)email: 文字列 (必須)phoneNumber: 文字列 (あってもなくても良い)isLeader: 真偽値 (必須)// ここに Staff インターフェースと変数 staff を定義してください
console.log(staff);
// @test: (function() { const testStaff: Staff = { id: 1, name: "A", email: "B", isLeader: true }; return testStaff.id === 1; })()
// @test: typeof staff === 'object' && typeof staff.id === 'number' && typeof staff.name === 'string' && typeof staff.email === 'string' && typeof staff.isLeader === 'boolean'
? をつけると、その項目は省略可能(あってもなくても良い)になります。boolean 型を使います。interface Staff {
id: number;
name: string;
email: string;
phoneNumber?: string; // Optional
isLeader: boolean;
}
const staff: Staff = {
id: 101,
name: "Shake",
email: "shake@example.com",
isLeader: true,
// phoneNumber はなくてもOK
};
extends)既存のインターフェースをベースに、新しい項目を追加した「拡張版」の型を作ることができます。
id, name 等)を何度も書く必要がなくなります(DRY原則)。// 基本となる型
interface User {
id: string;
name: string;
}
// User の中身を全て引き継ぎ、さらに department を追加
interface StaffUser extends User {
department: string;
role: "admin" | "member";
}
interface User {
id: string;
name: string;
}
interface StaffUser extends User {
department: string;
role: "admin" | "member";
}
const myProfile: StaffUser = {
id: "st-77",
name: "Shake",
department: "IT部",
role: "admin",
};
console.log(myProfile);
オブジェクトのプロパティで、**あってもなくても良い(任意)**なものを定義したいです。 プロパティ名の後ろに付ける記号は何ですか?
interface User {
id: number;
name: string;
// bio (自己紹介) は任意にしたい
bio____: string;
}
const user: User = { id: 1, name: "Shake" };
console.log(user);
// @test: (function() { const u: User = { id: 1, name: "A" }; return u.bio === undefined; })()
interface User {
id: number;
name: string;
bio?: string;
}
? をプロパティ名の後ろに付けると、そのプロパティは「省略可能」になります。
既存の型や、複数の型を組み合わせたものに 「独自の名前(別名)」 をつけることができます。
// 1. Union Types(共用体型): 複数の型の「どちらか」
type ID = string | number;
// 2. Literal Types(リテラル型): 特定の「値」そのものを型にする
// 「この3つの文字列以外は認めない」という強力な制約をかけられます
type Status = "pending" | "active" | "closed";
type ID = string | number;
type Status = "pending" | "active" | "closed";
const userId: ID = "u123"; // OK
const currentStatus: Status = "active"; // OK
// NG! 定義にない文字列を入れようとするとエラーになる (アンコメントすると型エラーを確認できます)
// const invalidStatus: Status = "done";
console.log({ userId, currentStatus });
変数が「文字列」または「数値」のどちらかの値を取りうる場合、どのように定義しますか? パイプ記号 | を使って書いてください。
// IDは文字列も数値も許容したい
type ID = string ____ number;
const id1: ID = "123";
const id2: ID = 123;
console.log(id1, id2);
// @test: (function() { const a: ID = "123"; const b: ID = 123; return true; })()
type ID = string | number;
| (パイプ) を使うと「A または B」という型(Union型)を定義できます。 これを Union Types と呼びます。
「共通の基本データ」と「特定の役割独自のデータ」を継承で表現してください。
EventItem: id(文字列), title(文字列) を持つ。StagePerformance: EventItem を継承し、以下の項目を追加。 startTime: 文字列status: "waiting" / "performing" / "finished" のいずれかopening としてください)// ここに型定義と変数 opening を記述してください
console.log(opening);
// @test: (function() { const item: StagePerformance = { id: "1", title: "A", startTime: "B", status: "performing" }; return item.status === "performing"; })()
// @test: typeof opening === 'object' && opening.status === 'performing' && typeof opening.startTime === 'string'
extends を使います。status のように決まった選択肢は Literal Types(文字列を | で繋ぐ)を Type Alias または直接指定して定義します。interface EventItem {
id: string;
title: string;
}
// Literal Types を使った状態定義
type PerformanceStatus = "waiting" | "performing" | "finished";
interface EventItem {
id: string;
title: string;
}
type PerformanceStatus = "waiting" | "performing" | "finished";
interface StagePerformance extends EventItem {
startTime: string;
status: PerformanceStatus;
}
const opening: StagePerformance = {
id: "stg-01",
title: "オープニングセレモニー",
startTime: "10:00",
status: "performing",
};
console.log(opening);
どちらも「型」を作るために使われますが、得意分野が異なります。
string | number のように、複数の型を合体させたものは Type Alias でしか名前をつけられません。"pending" | "active" のような「特定の選択肢のセット」を定義するのに最適です。結論: 最初は「オブジェクトなら Interface、それ以外(選択肢/複雑な合体)なら Type Alias」と使い分ければOK。
昨今の実務トレンド: React コミュニティなどを中心に、意図しない型のマージ(同名 Interface の自動結合)を防ぐなどの理由から 「プロジェクト内の型定義はすべて
Type Alias
(type) で統一する」 という現場も増えています。 本研修では歴史的経緯を踏まえて両方紹介しますが、「迷ったら
type
を使う」 と割り切ってしまっても構いません。
次のコードの ( ? ) に入る適切なキーワードは何でしょうか?
interface Animal {
name: string;
}
// ( ? ) の部分を extends に書き換えて、エラーを解消してください
interface Dog ( ? ) Animal {
bark(): void;
}
// 以下は動作確認用です (エラーが解消されると実行できます)
const dog: Dog = {
name: "ポチ",
bark() {
console.log("ワンワン!");
}
};
console.log(`${dog.name}が吠えました:`);
dog.bark();
答え:extends
extends キーワードを使います。ジェネリクス(Generics) とは、型そのものを「引数」として扱い、定義段階では確定させずに使用時に中身の型を指定する仕組みです。
データの枠組みは同じなのに、中身の型が違うだけで似たような定義を何度も作らなければならない不都合を解消します。
interface StringRes {
data: string;
status: number;
}
interface NumberRes {
data: number;
status: number;
}
// ... 型が増えるたびに新しい定義が必要になる (非効率)
変化する部分に T という 「型引数(プレースホルダ)」 を置き、「後で型が入る空欄」 を作ります。
interface ApiResponse { data: T; } (T は型が入るための変数のようなもの)ApiResponse と使うと、定義内の T が一斉に string に置き換わります。interface User {
id: string;
name: string;
email: string;
}
// T という「型を受け取るための変数」を用意した定義
interface ApiResponse<T> {
data: T;
status: number;
}
// 使用時に T の部分へ具体的な型(string)を流し込む
const textRes: ApiResponse<string> = { data: "success", status: 200 };
// 使用時に T の部分へ別の型(User)を流し込む
const userRes: ApiResponse<User> = {
data: { id: "1", name: "Alpha", email: "a@example.com" },
status: 200,
};
console.log(textRes);
console.log(userRes);
「どんな型でも入れられる箱」を定義したいですが、一度入れたらその型として扱いたいです。 型引数(Type Parameter)として一般的に使われるアルファベット1文字は何ですか?
interface Box<____> {
contents: ____;
}
const box: Box<string> = { contents: "hello" };
console.log(box.contents);
// @test: (function() { const b: Box<string> = { contents: "hello" }; return b.contents === "hello"; })()
interface Box<T> {
contents: T;
}
T (Type) が最もよく使われます。 使うときは Box のように具体的な型を指定します。
次の型定義を元に、数値(number)をデータとして持つ Item 型の変数を宣言してください。
interface Item<T> {
id: string;
content: T;
}
// ここに変数 myItem を宣言してください
console.log(myItem);
// @test: typeof myItem === 'object' && typeof myItem.id === 'string' && typeof myItem.content === 'number'
const myItem: Item<number> = {
id: "item-001",
content: 100,
};
の部分に number を流し込むことで、content プロパティが数値型として確定します。「異なる種類のデータ」を包む共通のレスポンス型をジェネリクスで作成してください。
ApiResponse: 「data: T型」と「status: 数値」を持つ。textResponse): ApiResponse を使って、文字列データを返す変数を作成。userResponse): ApiResponse を使って、前述の User 型を返す変数を作成。interface User {
id: string;
name: string;
email: string;
}
// ここに ApiResponse<T> を定義し、変数 textResponse, userResponse を作成してください
console.log(textResponse);
console.log(userResponse);
// @test: (function() { const r: ApiResponse<boolean> = { data: true, status: 200 }; return r.status === 200; })()
// @test: typeof textResponse === 'object' && typeof textResponse.data === 'string' && typeof textResponse.status === 'number'
// @test: typeof userResponse === 'object' && typeof userResponse.data === 'object' && typeof userResponse.data.id === 'string' && typeof userResponse.status === 'number'
をつけ、中身で T を型として使用します。interface User {
id: string;
name: string;
email: string;
}
interface ApiResponse<T> {
data: T;
status: number;
}
const textResponse: ApiResponse<string> = {
data: "成功しました",
status: 200,
};
const userResponse: ApiResponse<User> = {
data: { id: "1", name: "Shake", email: "shake@example.com" },
status: 200,
};
console.log(textResponse);
console.log(userResponse);
: string のように型を明示することで、エディタの強力な補完と保護を受けられる。extends で再利用・拡張することで共通化を促進する。)し、構造は同じだが中身が異なるデータ(API レスポンス等)を効率的かつ安全に定義する。次は、この型安全な環境が動作する舞台「Cloudflare」について学びます。