JS/TS基礎 - 第2章:インターフェースと実践的な型定義

JS/TS基礎 - インターフェースと実践的な型定義

より実践的で再利用可能な型定義の方法を学ぼう

第1章で学んだ基本的な型を使って、より実践的な型定義の方法を学びましょう。


Type Alias - 型に名前をつける

Type Aliasを使うと、複雑な型に名前をつけて再利用できます。

基本的な使い方

// 長い型を何度も書くのは大変...
const user1: { id: number; name: string; email: string } = {
  id: 1,
  name: "太郎",
  email: "[email protected]"
};

const user2: { id: number; name: string; email: string } = {
  id: 2,
  name: "花子",
  email: "[email protected]"
};

// Type Aliasで名前をつける
type User = {
  id: number;
  name: string;
  email: string;
};

// シンプルに書ける!
const user1: User = {
  id: 1,
  name: "太郎",
  email: "[email protected]"
};

const user2: User = {
  id: 2,
  name: "花子",
  email: "[email protected]"
};

ポイント:

  • type キーワードを使う
  • 型に好きな名前をつけられる
  • 同じ型を何度も使う場合に便利

React開発での実践例

// ユーザー情報の型
type User = {
  id: number;
  name: string;
  email: string;
  avatar?: string;  // オプショナル
};

// Propsの型
type UserCardProps = {
  user: User;
  onClick: () => void;
};

function UserCard({ user, onClick }: UserCardProps) {
  return (
    <div onClick={onClick}>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

// 使用例
const userData: User = {
  id: 1,
  name: "太郎",
  email: "[email protected]"
};

<UserCard user={userData} onClick={() => console.log("クリック")} />

Union型とLiteral型 - より柔軟な型定義

Union型 - 複数の型を許容する

Union型を使うと、「AまたはB」という型を定義できます。

// IDは文字列でも数値でもOK
type ID = string | number;

let userId: ID;
userId = "abc123";  // OK
userId = 12345;     // OK
// userId = true;   // エラー!booleanは許可されていない

|(パイプ)で型を区切ります。

React開発での実践例

// データ読み込み中はnull、読み込み後はデータ
type UserData = User | null;

const [userData, setUserData] = useState<UserData>(null);

if (userData === null) {
  return <p>読み込み中...</p>;
}

return <p>こんにちは、{userData.name}さん!</p>;

Literal型 - 特定の値だけを許可する

Literal型を使うと、特定の値だけを許可する型を定義できます。

// この3つの文字列しか入らない
type Status = "success" | "error" | "loading";

let status: Status;
status = "success";  // OK
status = "error";    // OK
status = "loading";  // OK
// status = "pending";  // エラー!この値は許可されていない

React開発での実践例

// ボタンのバリアント(見た目のパターン)
type ButtonVariant = "primary" | "secondary" | "danger";

type ButtonProps = {
  variant: ButtonVariant;
  children: React.ReactNode;
};

function Button({ variant, children }: ButtonProps) {
  const colors = {
    primary: "bg-blue-500",
    secondary: "bg-gray-500",
    danger: "bg-red-500"
  };
  
  return (
    <button className={colors[variant]}>
      {children}
    </button>
  );
}

// 使用例
<Button variant="primary">保存</Button>
<Button variant="danger">削除</Button>
// <Button variant="warning">警告</Button>  // エラー!

Literal型のメリット:

  • ✅ タイプミスを防げる
  • ✅ エディタの自動補完が効く
  • ✅ どんな値が使えるか一目でわかる

void型 - 戻り値がない関数

void型は、関数が値を返さない時に使います。

基本的な使い方

// 戻り値がない関数
function logMessage(message: string): void {
  console.log(message);
  // returnしない
}

// アロー関数でも同じ
const showAlert = (message: string): void => {
  alert(message);
};

React開発での実践例

イベントハンドラはvoid型を返します。

// Propsでイベントハンドラを受け取る
type ButtonProps = {
  onClick: () => void;  // voidを返す関数
  children: React.ReactNode;
};

function Button({ onClick, children }: ButtonProps) {
  return <button onClick={onClick}>{children}</button>;
}

// 使用例
function App() {
  const handleClick = (): void => {
    console.log("ボタンがクリックされました");
  };

  return <Button onClick={handleClick}>クリック</Button>;
}

よく見るパターン

function Counter() {
  const [count, setCount] = useState(0);

  // イベントハンドラはvoid型を返す
  const handleClick = (): void => {
    setCount(count + 1);
  };

  const handleSubmit = (e: React.FormEvent): void => {
    e.preventDefault();
    // フォーム送信処理
  };

  return (
    <div>
      <p>カウント: {count}</p>
      <button onClick={handleClick}>+1</button>
    </div>
  );
}

タプル型 - 固定長の配列

タプル型は、要素数と各要素の型が決まっている配列です。

基本的な使い方

// 普通の配列 - 何個でも入る、全て同じ型
const numbers: number[] = [1, 2, 3, 4, 5];

// タプル - 2つだけ、1つ目はstring、2つ目はnumber
const person: [string, number] = ["太郎", 25];

// 分割代入で取り出せる
const [name, age] = person;
console.log(name);  // "太郎" (string型)
console.log(age);   // 25 (number型)

// エラー!要素数が違う
// const person: [string, number] = ["太郎", 25, "追加"];

// エラー!型が違う
// const person: [string, number] = [25, "太郎"];

タプルの特徴:

  • 要素数が固定
  • 各位置の型が決まっている
  • 順序が重要

React開発での実践例 - useState

実は、useStateの戻り値がタプルです!

より深く理解するために: 第4章「React Hooks - useState」でタプル型の実践的な使い方を学んだ後、このセクションを読み返すとより理解が深まります。

const [count, setCount] = useState(0);

useStateの型定義を見てみると:

// useStateの型定義(簡略版)
function useState<T>(initialValue: T): [T, (value: T) => void]
//                                      ↑
//                                    タプル型!

// つまり
const result = useState(0);
// result の型: [number, (value: number) => void]

// 分割代入で取り出す
const [count, setCount] = result;
// count: number
// setCount: (value: number) => void

なぜタプルを使うのか?

// もしオブジェクトだったら...
const { value, setValue } = useState(0);  // 名前が固定されてしまう

// タプルなら...
const [count, setCount] = useState(0);      // 好きな名前をつけられる
const [name, setName] = useState("");       // 何度も使える
const [isOpen, setIsOpen] = useState(false);

複数の値を返す関数

// 座標を返す関数
function getMousePosition(): [number, number] {
  // マウスの位置を取得(例)
  return [100, 200];  // [x座標, y座標]
}

const [x, y] = getMousePosition();
console.log(`x: ${x}, y: ${y}`);  // "x: 100, y: 200"

// カスタムフック
function useFormField(initialValue: string): [string, (e: React.ChangeEvent<HTMLInputElement>) => void] {
  const [value, setValue] = useState(initialValue);
  
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setValue(e.target.value);
  };
  
  return [value, handleChange];  // タプルで返す
}

// 使用例
function LoginForm() {
  const [email, handleEmailChange] = useFormField("");
  const [password, handlePasswordChange] = useFormField("");
  
  return (
    <form>
      <input value={email} onChange={handleEmailChange} />
      <input value={password} onChange={handlePasswordChange} type="password" />
    </form>
  );
}

インターフェース (Interface)

インターフェースは、オブジェクトの「形」を定義する機能です。Type Aliasと似ていますが、いくつか違いがあります。

基本的な使い方

// インターフェースの定義
interface User {
  name: string;
  age: number;
  email: string;
}

// インターフェースを使う
const user1: User = {
  name: "太郎",
  age: 25,
  email: "[email protected]"
};

const user2: User = {
  name: "花子",
  age: 23,
  email: "[email protected]"
};

オプショナルプロパティ

必須ではないプロパティには ? をつけます。

interface User {
  name: string;
  age: number;
  email: string;
  phone?: string;  // phoneは省略可能
  avatar?: string; // avatarも省略可能
}

// phoneがなくてもOK
const user1: User = {
  name: "太郎",
  age: 25,
  email: "[email protected]"
};

// phoneがあってもOK
const user2: User = {
  name: "花子",
  age: 23,
  email: "[email protected]",
  phone: "090-1234-5678"
};

readonlyプロパティ

変更を禁止したいプロパティには readonly をつけます。

interface User {
  readonly id: number;  // IDは変更できない
  name: string;
  email: string;
}

const user: User = {
  id: 1,
  name: "太郎",
  email: "[email protected]"
};

user.name = "次郎";  // OK
// user.id = 2;       // エラー!idは変更できない

インターフェースの拡張 (extends)

既存のインターフェースを拡張して、新しいプロパティを追加できます。

// 基本のUser
interface User {
  name: string;
  email: string;
}

// Userを拡張してAdminを作る
interface Admin extends User {
  role: string;
  permissions: string[];
}

const admin: Admin = {
  name: "管理者",
  email: "[email protected]",
  role: "super_admin",
  permissions: ["read", "write", "delete"]
};

interfaceとtype aliasの違い

// interface: オブジェクトの形を定義するのに適している
interface User {
  name: string;
  age: number;
}

// type: Union型やLiteral型も定義できる
type ID = string | number;
type Status = "success" | "error" | "loading";

// type: オブジェクトも定義できる
type User = {
  name: string;
  age: number;
};

使い分けのコツ:

  • オブジェクトの形を定義する場合 → interface を使うことが多い
  • Union型やLiteral型を定義する場合 → type を使う
  • React開発では、Propsの型定義に interface がよく使われる

React開発での実践例

// Propsの型をinterfaceで定義
interface UserCardProps {
  user: {
    name: string;
    age: number;
    email: string;
  };
  onEdit?: () => void;  // オプショナル
}

function UserCard({ user, onEdit }: UserCardProps) {
  return (
    <div>
      <h2>{user.name}</h2>
      <p>年齢: {user.age}歳</p>
      <p>メール: {user.email}</p>
      {onEdit && <button onClick={onEdit}>編集</button>}
    </div>
  );
}

// さらに改善:Userの型も分離
interface User {
  name: string;
  age: number;
  email: string;
}

interface UserCardProps {
  user: User;
  onEdit?: () => void;
}

function UserCard({ user, onEdit }: UserCardProps) {
  return (
    <div>
      <h2>{user.name}</h2>
      <p>年齢: {user.age}歳</p>
      <p>メール: {user.email}</p>
      {onEdit && <button onClick={onEdit}>編集</button>}
    </div>
  );
}

ジェネリクス - ライブラリの型を読むための基礎知識

ジェネリクスは、「型をパラメータとして受け取る」機能です。自分で書くことは少ないですが、ReactやTypeScriptのライブラリを使う時に頻繁に見かけます。

ジェネリクスとは?

<T> のような書き方を見たことがありませんか?これがジェネリクスです。

// よく見るパターン
const [count, setCount] = useState<number>(0);
const [name, setName] = useState<string>("");
const [users, setUsers] = useState<User[]>([]);

// 配列の型
const numbers: Array<number> = [1, 2, 3];
const names: Array<string> = ["太郎", "花子"];

<number><string> の部分が「型パラメータ」です。

なぜジェネリクスが必要?

例えば、useStateは数値でも文字列でもオブジェクトでも使えます。これはジェネリクスのおかげです。

// useStateの型定義(簡略版)
function useState<T>(initialValue: T): [T, (value: T) => void]
//              ↑型パラメータ

// 使用例
const [count, setCount] = useState<number>(0);
// Tがnumberになる → [number, (value: number) => void]

const [name, setName] = useState<string>("");
// Tがstringになる → [string, (value: string) => void]

よく見るパターン

// useState - 型を明示する
const [user, setUser] = useState<User | null>(null);

// useState - 型推論に任せる(これでもOK)
const [count, setCount] = useState(0);  // number型と推論される

// 配列
const numbers: Array<number> = [1, 2, 3];
// これと同じ意味
const numbers: number[] = [1, 2, 3];

// Promiseの型
const fetchUser = async (): Promise<User> => {
  const response = await fetch("/api/user");
  return response.json();
};

基本的な書き方(参考)

自分で書くことは少ないですが、簡単な例を見てみましょう。

// 配列の最初の要素を返す関数
function first<T>(arr: T[]): T {
  return arr[0];
}

// どんな型でも使える!
const num = first([1, 2, 3]);        // numはnumber型
const str = first(["a", "b", "c"]);  // strはstring型

実際の開発では:

  • ジェネリクスを自分で書くことは少ない
  • useStateやPromiseなど、既存のものを使う時に見かける
  • <>の中に型を書く」と覚えておけばOK

型アサーション - 型を明示的に指定する

型アサーションは、「この値は○○型だよ」とTypeScriptに教える機能です。

基本的な使い方

const value: any = "hello";
const length: number = (value as string).length;

// 別の書き方(古い書き方、あまり使わない)
const length: number = (<string>value).length;

as 型名 で型を指定します。

React開発での実践例

例1: DOM要素の取得

const button = document.getElementById("myButton") as HTMLButtonElement;
button.disabled = true;

const input = document.querySelector("input") as HTMLInputElement;
console.log(input.value);

例2: イベントハンドラ

function handleChange(e: Event) {
  const target = e.target as HTMLInputElement;
  console.log(target.value);
}

// React��イベントハンドラ
function handleSubmit(e: React.FormEvent) {
  const form = e.target as HTMLFormElement;
  const formData = new FormData(form);
}

⚠️ 注意点

型アサーションは「あなたがTypeScriptより詳しい」と宣言することです。間違った型を指定すると実行時エラーになります。

// 危険な例
const value: any = 123;
const text = value as string;
console.log(text.toUpperCase());  // 実行時エラー!

// 本当に確信がある時だけ使う
const button = document.getElementById("myButton");
if (button) {
  // buttonが存在することを確認してから使う
  const htmlButton = button as HTMLButtonElement;
  htmlButton.disabled = true;
}

使い方のコツ:

  • 本当に型が分かっている時だけ使う
  • DOM要素の取得など、限定的な場面で使う
  • 多用しない(型推論やUnion型で解決できないか考える)

まとめ

この章では、実践的な型定義の方法を学びました。

型定義の基本

  • Type Alias: 型に名前をつけて再利用する
  • Union型: 複数の型を許容する(A | B
  • Literal型: 特定の値だけを許可する

実践的な型

  • void型: 戻り値がない関数(イベントハンドラでよく使う)
  • タプル型: 固定長の配列(useStateの戻り値など)

オブジェクトの型定義

  • interface: オブジェクトの形を定義、拡張可能
  • オプショナルプロパティ(?)、readonly
  • interfaceとtype aliasの使い分け

応用

  • ジェネリクス: <T> のように使う(主に既存のライブラリで見かける)
  • 型アサーション: as 型名 で型を指定(慎重に使う)

💡 次のステップ

これでTypeScriptの基礎は完璧です!次はReactの基礎を学んで、実際にコンポーネントを作っていきましょう。