データの「種類」を定義するルール

これまで変数や定数を学んできましたが、プログラムが扱うすべてのデータには「型(Type)」というデータの種類が決まっています。

「型」が必要な理由: 「名前(文字列)に 10 を足す」といった、人間から見たら明らかに 「意味の通らない操作」 を、コンピュータが事前(実行前)に見つけるための手がかりになります。

型がない世界のリスク

JavaScript は非常に自由ですが、その自由さがバグの原因になります。

TypeScript がもたらす「安心感」

TypeScript は JavaScript に 静的型付け を追加した言語です。

JavaScript の罠:「気づけない」こと

function buy(price) {
  return price * 1.1;
}
buy("1,000円"); 

TypeScript の安心感:「書いている時に気づく」

function safeBuy(price: number) {
  return price * 1.1;
}
safeBuy("1,000円");
//      ^^^^^^^^^
// Argument of type 'string' is not assignable to parameter of type 'number'.

なぜこれが「一番重要」なのか?

強力な補完(オートコンプリート)

const user = { name: "Shake", age: 20 };

// 下の行のコメントを外して、「user.」の後にタイピングしてみてください
// console.log(user.name);

変数と関数の型アノテーション (1/2)

型アノテーション(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;
}

変数と関数の型アノテーション (2/2)

// アロー関数の場合
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;
}

any 型:すべてを許容する「禁じ手」

any 型を指定すると、その変数に対する TypeScript の型チェックがすべて無効化されます。

let value: any = "Shake";

value = 123; // OK (型のエラーが出ない)
value.push(); // OK (?) ... 実際には数値に push はないので実行時にエラーになる!

代替案:より安全な unknown 型

どうしても型が分からない場合、現代のTypeScriptでは any ではなく unknown 型を使うのがベストプラクティスです。

let safeValue: unknown = "Shake";

// safeValue.push(); // エラー!「よくわからないものに push はできません」と怒ってくれる

if (typeof safeValue === "string") {
  // ここからは string として扱える(型の絞り込み)
  console.log(safeValue.length);
}

エラーを強制的に黙らせるコメント

どうしても型エラーが解決できない、あるいはライブラリ側の型定義が間違っている場合に、コンパイルエラーを無理やり消すコメントがあります。

// @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)

Interface (インターフェース)

オブジェクト(複数のデータがまとまったもの)が、「どのようなプロパティを持ち、それぞれの値が何型か」という「設計図(形)」 を定義します。

Interfaceの例 (1/2)

interface User {
  id: string;
  name: string;
  email: string;
  // プロパティ名の後ろに ? をつけると「あってもなくても良い(任意)」になります
  age?: number;
}

// OK: 全ての必須項目が揃っている
const staff: User = {
  id: "st-01",
  name: "Taro",
  email: "taro@example.com",
};

Interfaceの例 (2/2)

// NG: name が欠けているためエラーになる
const visitor: User = {
  id: "v-02",
  email: "visitor@example.com",
};

以下の条件を満たす Staff 型を interface で定義し、変数を作成してください。

  1. id: 数値 (必須)
  2. name: 文字列 (必須)
  3. email: 文字列 (必須)
  4. phoneNumber: 文字列 (あってもなくても良い)
  5. 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'

練習問題: 実行委員スタッフの管理 (ヒント)

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
};

Interface の継承 (extends)

既存のインターフェースをベースに、新しい項目を追加した「拡張版」の型を作ることができます。

Interface の継承 (1/2)

// 基本となる型
interface User {
  id: string;
  name: string;
}

// User の中身を全て引き継ぎ、さらに department を追加
interface StaffUser extends User {
  department: string;
  role: "admin" | "member";
}

Interface の継承 (2/2)

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;
}

? をプロパティ名の後ろに付けると、そのプロパティは「省略可能」になります。

Type Alias (型別名) (1/2)

既存の型や、複数の型を組み合わせたものに 「独自の名前(別名)」 をつけることができます。

// 1. Union Types(共用体型): 複数の型の「どちらか」
type ID = string | number;

// 2. Literal Types(リテラル型): 特定の「値」そのものを型にする
// 「この3つの文字列以外は認めない」という強力な制約をかけられます
type Status = "pending" | "active" | "closed";

Type Alias (型別名) (2/2)

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 と呼びます。

「共通の基本データ」と「特定の役割独自のデータ」を継承で表現してください。

  1. 基本型 EventItem: id(文字列), title(文字列) を持つ。
  2. 拡張型 StagePerformance: EventItem を継承し、以下の項目を追加。
    • startTime: 文字列
    • status: "waiting" / "performing" / "finished" のいずれか
  3. 変数作成: 作成した型を使って、現在の状態が "performing" のデータを作成してください。(変数名は 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'

練習問題: イベント予約システム (ヒント)

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);

Interface と Type Alias の使い分け (1/2)

どちらも「型」を作るために使われますが、得意分野が異なります。

Interface は「拡張可能な設計図」

Interface と Type Alias の使い分け (2/2)

Type Alias は「柔軟なあだ名」

結論: 最初は「オブジェクトなら 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

定義時に型を確定させない仕組み

ジェネリクス(Generics) とは、型そのものを「引数」として扱い、定義段階では確定させずに使用時に中身の型を指定する仕組みです。

1. なぜ必要なのか(非効率な例)

データの枠組みは同じなのに、中身の型が違うだけで似たような定義を何度も作らなければならない不都合を解消します。

interface StringRes {
  data: string;
  status: number;
}
interface NumberRes {
  data: number;
  status: number;
}
// ... 型が増えるたびに新しい定義が必要になる (非効率)

2. ジェネリクスによる解決

変化する部分に T という 「型引数(プレースホルダ)」 を置き、「後で型が入る空欄」 を作ります。

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,
};

「異なる種類のデータ」を包む共通のレスポンス型をジェネリクスで作成してください。

  1. ジェネリック型 ApiResponse: 「data: T型」と「status: 数値」を持つ。
  2. 変数 1 (変数名 textResponse): ApiResponse を使って、文字列データを返す変数を作成。
  3. 変数 2 (変数名 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'

練習問題: 汎用的な API レスポンス (ヒント)

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);

本セクションのまとめ

次は、この型安全な環境が動作する舞台「Cloudflare」について学びます。