ReactでTypeScriptを使った子コンポーネントへの型情報の渡し方

React開発では、コンポーネント間でデータをやり取りする際に、Props(プロパティ)を使用します。このとき、TypeScriptを活用することで型の安全性を確保し、バグを未然に防ぐことが可能です。特に、親コンポーネントから子コンポーネントにデータを渡す際に、型情報を明確に定義しておくことで、コードの読みやすさや保守性が向上します。本記事では、ReactとTypeScriptを組み合わせて子コンポーネントに型情報を渡す方法について、基本的な概念から応用的なテクニックまでを詳しく解説します。

目次

ReactとTypeScriptの基礎知識

Reactは、ユーザーインターフェースを構築するための人気のあるJavaScriptライブラリであり、コンポーネントベースの開発スタイルが特徴です。一方、TypeScriptは静的型付けを提供するJavaScriptのスーパーセットで、開発時に型の安全性を確保できるツールです。

ReactとTypeScriptを組み合わせる利点

ReactとTypeScriptを組み合わせることで、以下のような利点を得られます:

  • 型チェックによるエラーの削減:型情報を使用することで、予期しないエラーを事前に防げます。
  • コードの明確性:型が明確に定義されているため、他の開発者や将来の自分にとってコードが理解しやすくなります。
  • 開発効率の向上:エディタの補完機能が強化され、記述ミスを減らすことができます。

ReactプロジェクトでTypeScriptを設定する方法

ReactでTypeScriptを使用するには、以下の手順でプロジェクトを設定します:

1. 新規プロジェクトの作成

以下のコマンドでTypeScriptを有効にしたReactプロジェクトを作成できます:

npx create-react-app my-app --template typescript

2. 既存プロジェクトへのTypeScript導入

すでにReactプロジェクトがある場合、以下のコマンドでTypeScriptをインストールします:

npm install --save typescript @types/react @types/react-dom

その後、tsconfig.jsonを作成し、TypeScript設定を追加します。

基本的なTypeScript構文

Reactコンポーネントに型情報を追加する際には、以下のTypeScript構文を頻繁に使用します:

  • 型定義:変数や関数の型を明示します。
const greet: string = "Hello, TypeScript!";
  • インターフェース:複数のプロパティを持つオブジェクトの型を定義します。
interface User {
  name: string;
  age: number;
}
const user: User = { name: "Alice", age: 25 };

これらの基本を押さえることで、ReactとTypeScriptを効率よく組み合わせた開発が可能になります。

TypeScriptでPropsの型を定義する方法

Reactコンポーネントで型安全性を確保するためには、Props(プロパティ)に型情報を付けることが重要です。TypeScriptを使用すると、親コンポーネントから子コンポーネントに渡されるデータの形式を明確に定義できます。

基本的なPropsの型定義

Propsの型を定義するには、インターフェースタイプエイリアスを使用します。以下に基本的な例を示します:

1. 型の定義

以下のように、子コンポーネントが受け取るPropsの型を定義します:

interface GreetingProps {
  name: string;
}

2. 型をコンポーネントに適用

定義した型をReactコンポーネントに適用するには、以下のように記述します:

const Greeting: React.FC<GreetingProps> = ({ name }) => {
  return <h1>Hello, {name}!</h1>;
};

この例では、nameプロパティが文字列型であることを指定しています。

型定義による利点

  • コードの安全性:間違った型の値を渡すと、TypeScriptがエラーを報告します。
  • 開発効率の向上:エディタが型情報を元に補完を提供します。

複数のPropsを扱う場合

複数のプロパティを扱う場合も同様に型を定義できます。

interface UserProfileProps {
  name: string;
  age: number;
  isAdmin?: boolean; // オプショナルなプロパティ
}

const UserProfile: React.FC<UserProfileProps> = ({ name, age, isAdmin }) => {
  return (
    <div>
      <h2>{name}</h2>
      <p>Age: {age}</p>
      {isAdmin && <p>Admin User</p>}
    </div>
  );
};

ここでは、isAdminがオプショナル(省略可能)であることを?で示しています。

Propsに型を付ける際の注意点

  • 型の詳細化:Propsが複雑なオブジェクトや配列の場合、ネストされた型を明確に定義する必要があります。
  • Union型:異なる型の値を受け取る場合はUnion型を活用します。

Union型の例

interface NotificationProps {
  message: string | null;
  type: "success" | "error" | "info";
}

const Notification: React.FC<NotificationProps> = ({ message, type }) => {
  if (!message) return null;
  return <div className={`notification-${type}`}>{message}</div>;
};

型定義を適切に行うことで、コードの予測可能性と保守性が大幅に向上します。次に、さらに応用的な型定義について解説します。

型定義の応用例:Union型とOptional型の活用

Reactのコンポーネント開発では、複雑なPropsを扱う場面が頻繁にあります。TypeScriptを使用すると、Union型やOptional型を活用して柔軟で強力な型定義を行うことができます。

Union型の活用

Union型を使用すると、複数の型のいずれかを受け入れるPropsを定義できます。これにより、異なるデータ形式を許容する柔軟性を持たせることができます。

例:状態に応じたコンポーネントの動作

以下の例では、statusプロパティが異なる値を取ることを想定しています:

interface StatusProps {
  status: "loading" | "success" | "error";
}

const StatusMessage: React.FC<StatusProps> = ({ status }) => {
  switch (status) {
    case "loading":
      return <p>Loading...</p>;
    case "success":
      return <p>Operation Successful!</p>;
    case "error":
      return <p>Error Occurred.</p>;
    default:
      return null;
  }
};

Union型を使用することで、statusが定義された値以外を取ることを防ぎ、コードの予測性が高まります。

Optional型の活用

Optional型を使用すると、Propsが必須ではないことを表現できます。オプショナルなPropsを定義する際には、型の後に?を付けます。

例:オプショナルなPropsを持つコンポーネント

以下の例では、subtitleプロパティが省略可能であることを示しています:

interface HeaderProps {
  title: string;
  subtitle?: string; // オプショナルプロパティ
}

const Header: React.FC<HeaderProps> = ({ title, subtitle }) => {
  return (
    <header>
      <h1>{title}</h1>
      {subtitle && <h2>{subtitle}</h2>}
    </header>
  );
};

オプショナルなPropsを持つことで、呼び出し側が柔軟にコンポーネントを利用できます。

複合的な型定義の例

Union型とOptional型を組み合わせて、より複雑な型を表現することも可能です。

例:通知メッセージコンポーネント

interface NotificationProps {
  type: "info" | "warning" | "error";
  message: string;
  actionLabel?: string; // オプショナル
  onActionClick?: () => void; // オプショナル
}

const Notification: React.FC<NotificationProps> = ({
  type,
  message,
  actionLabel,
  onActionClick,
}) => {
  return (
    <div className={`notification-${type}`}>
      <p>{message}</p>
      {actionLabel && onActionClick && (
        <button onClick={onActionClick}>{actionLabel}</button>
      )}
    </div>
  );
};

このコンポーネントでは、actionLabelonActionClickがセットで使用される場合にのみボタンが表示されます。

応用的な型定義を行う際の注意点

  • 型の複雑化を避ける:型が複雑になりすぎる場合は、リファクタリングしてシンプルに保つ。
  • 型エイリアスを活用:可読性を向上させるため、複雑な型をエイリアスとして分離。

Union型とOptional型を効果的に利用することで、柔軟で安全なコンポーネントを作成でき、Reactアプリケーションの品質向上に大きく寄与します。次は型の再利用性を高める設計について解説します。

コンポーネントの再利用性を高める型の設計

Reactの開発では、コンポーネントの再利用性を意識した設計が重要です。型定義を工夫することで、汎用性が高く、さまざまな状況で使い回せるコンポーネントを構築できます。本節では、再利用性を高めるための型設計のベストプラクティスを紹介します。

ジェネリック型の活用

ジェネリック型を使うことで、さまざまな型に対応可能な柔軟なコンポーネントを作成できます。

例:汎用的なリストコンポーネント

以下は、任意のデータ型のリストをレンダリングするコンポーネントの例です:

interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => JSX.Element;
}

const List = <T,>({ items, renderItem }: ListProps<T>): JSX.Element => {
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{renderItem(item)}</li>
      ))}
    </ul>
  );
};

// 使用例
const App = () => {
  const users = [
    { id: 1, name: "Alice" },
    { id: 2, name: "Bob" },
  ];
  return (
    <List
      items={users}
      renderItem={(user) => <p>{user.name}</p>}
    />
  );
};

このように、ジェネリック型を使用することで、多様なデータ型に対応したコンポーネントが構築できます。

デフォルト値を活用した型設計

Propsにデフォルト値を設定すると、呼び出し側が不要な記述をせずに済むため、再利用性が向上します。

例:デフォルト値を持つボタンコンポーネント

interface ButtonProps {
  label: string;
  onClick: () => void;
  variant?: "primary" | "secondary"; // オプショナル
}

const Button: React.FC<ButtonProps> = ({ label, onClick, variant = "primary" }) => {
  return <button className={`btn-${variant}`} onClick={onClick}>{label}</button>;
};

// 使用例
<Button label="Click Me" onClick={() => alert("Clicked!")} />;

ここでは、variantプロパティにデフォルト値を設定することで、呼び出し側がオプショナルなプロパティを省略できるようにしています。

複数の型を統合して再利用性を向上

複数のコンポーネント間で共通の型を定義すると、コードの重複を減らし、再利用性を向上させることができます。

例:共通型の定義

interface BaseProps {
  id: string;
  className?: string;
}

interface CardProps extends BaseProps {
  title: string;
  content: string;
}

const Card: React.FC<CardProps> = ({ id, className, title, content }) => {
  return (
    <div id={id} className={`card ${className || ""}`}>
      <h2>{title}</h2>
      <p>{content}</p>
    </div>
  );
};

この例では、BasePropsを共通の型として再利用し、複数のコンポーネントで活用しています。

型設計のポイント

  1. シンプルに保つ:複雑な型定義を避け、読みやすさを意識する。
  2. ジェネリック型を活用:柔軟性を高めるため、ジェネリック型を適切に利用する。
  3. 共通型の分離:複数のコンポーネントで使用する型は、共通の型として分離する。

型設計に工夫を加えることで、再利用性の高いコンポーネントを作成でき、Reactアプリケーション全体の保守性と効率性が向上します。次に、型チェックで発生しやすいエラーとその解決策を紹介します。

Propsの型チェックで発生しやすいエラーと解決策

TypeScriptを使用することで、ReactコンポーネントのPropsに対する型チェックが可能になります。しかし、型チェックによるエラーが発生する場合も少なくありません。このセクションでは、よくある型チェックのエラーとその解決策を紹介します。

1. 必須Propsの不足

TypeScriptでは、定義された必須Propsが渡されない場合にエラーが発生します。

例:エラーの発生

interface GreetingProps {
  name: string;
}

const Greeting: React.FC<GreetingProps> = ({ name }) => {
  return <p>Hello, {name}!</p>;
};

// エラー:'name' が渡されていない
<Greeting />;

解決策

  1. 必須Propsをすべて渡すようにする。
<Greeting name="Alice" />;
  1. 必須ではない場合は、オプショナル型を使用する。
interface GreetingProps {
  name?: string; // オプショナル型
}

const Greeting: React.FC<GreetingProps> = ({ name = "Guest" }) => {
  return <p>Hello, {name}!</p>;
};

2. 型の不一致

渡されるPropsが定義された型と一致しない場合、TypeScriptはエラーを報告します。

例:エラーの発生

interface UserProps {
  age: number;
}

const User: React.FC<UserProps> = ({ age }) => {
  return <p>Age: {age}</p>;
};

// エラー:'string'型の値を'number'型に割り当てることはできません
<User age="25" />;

解決策

型定義に一致する値を渡します。

<User age={25} />;

3. オプショナルPropsの未定義エラー

オプショナルなPropsを使用する際に、その値が未定義である場合のエラーが発生することがあります。

例:エラーの発生

interface ProfileProps {
  nickname?: string;
}

const Profile: React.FC<ProfileProps> = ({ nickname }) => {
  return <p>Nickname: {nickname.toUpperCase()}</p>; // エラー:nickname が undefined の可能性があります
};

解決策

  1. デフォルト値を設定する。
const Profile: React.FC<ProfileProps> = ({ nickname = "Guest" }) => {
  return <p>Nickname: {nickname.toUpperCase()}</p>;
};
  1. オプショナルチェーンや条件分岐を使用する。
const Profile: React.FC<ProfileProps> = ({ nickname }) => {
  return <p>Nickname: {nickname?.toUpperCase() || "Guest"}</p>;
};

4. 配列やオブジェクトの型チェックエラー

複雑な型(配列やオブジェクト)に対する型チェックでエラーが発生することがあります。

例:エラーの発生

interface Task {
  id: number;
  title: string;
}

interface TaskListProps {
  tasks: Task[];
}

const TaskList: React.FC<TaskListProps> = ({ tasks }) => {
  return (
    <ul>
      {tasks.map((task) => (
        <li key={task.id}>{task.title}</li>
      ))}
    </ul>
  );
};

// エラー:tasks に正しい型の配列が渡されていない
<TaskList tasks={[{ id: "1", title: "Test Task" }]} />;

解決策

  1. 型定義に一致するデータを渡します。
<TaskList tasks={[{ id: 1, title: "Test Task" }]} />;
  1. 型エラーをチェックしてデータを変換します。
const tasks = [{ id: "1", title: "Test Task" }].map((task) => ({
  id: Number(task.id),
  title: task.title,
}));
<TaskList tasks={tasks} />;

5. 非対応のUnion型エラー

Union型を使用している場合、渡された値が定義された型に一致しないとエラーが発生します。

例:エラーの発生

interface AlertProps {
  type: "success" | "error";
  message: string;
}

const Alert: React.FC<AlertProps> = ({ type, message }) => {
  return <div className={`alert-${type}`}>{message}</div>;
};

// エラー:'warning' は 'success' | 'error' に割り当てられません
<Alert type="warning" message="This is a warning!" />;

解決策

Union型に一致する値を使用します。

<Alert type="success" message="This is a success message!" />;

型チェックエラーを防ぐためのコツ

  1. 型定義を正確に行う:Propsの可能な値をすべて考慮する。
  2. 型チェックツールを活用:エディタやTypeScriptの警告を参考にして修正を行う。
  3. 型に柔軟性を持たせる:Union型やOptional型を適切に使用し、柔軟な設計を目指す。

これらのエラーと解決策を理解することで、ReactとTypeScriptをより効率的に活用し、安全で堅牢なコードを書くことができます。次に、型情報を活用した状態管理とイベント処理について解説します。

型情報を活用した状態管理とイベント処理

Reactの開発では、状態管理とイベント処理が重要な要素です。TypeScriptを使用することで、状態やイベントに型情報を適用し、エラーを防ぎながら効率的にコードを書くことが可能になります。このセクションでは、状態管理とイベント処理での型情報の活用方法を解説します。

状態管理に型情報を適用する

Reactで状態を管理する際、useStateフックを使用します。TypeScriptを用いると、状態の型を明確に指定できます。

例:基本的な状態管理

以下の例では、数値型の状態を管理します:

import React, { useState } from "react";

const Counter: React.FC = () => {
  const [count, setCount] = useState<number>(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
};

ここで、useState<number>(0)により、状態countが数値型であることを指定しています。この型情報によって、setCountに誤った型の値を渡した場合にエラーが発生します。

例:オブジェクト型の状態管理

複雑なオブジェクトを状態として管理する場合も型を定義できます。

interface User {
  id: number;
  name: string;
}

const UserProfile: React.FC = () => {
  const [user, setUser] = useState<User | null>(null);

  return (
    <div>
      {user ? <p>Welcome, {user.name}!</p> : <p>No user logged in</p>}
      <button
        onClick={() => setUser({ id: 1, name: "Alice" })}
      >
        Login
      </button>
    </div>
  );
};

この例では、状態userUser型またはnullであることを明確にしています。

イベント処理に型情報を適用する

Reactのイベントハンドラーにも型情報を付けることで、イベントオブジェクトの誤った使用を防げます。

例:基本的なクリックイベント

以下はクリックイベントに型情報を追加した例です:

const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
  console.log("Button clicked:", event.currentTarget);
};

const Button: React.FC = () => {
  return <button onClick={handleClick}>Click me</button>;
};

ここで、React.MouseEvent<HTMLButtonElement>により、クリックされた要素が<button>であることを指定しています。

例:フォームの送信イベント

フォーム送信時のイベントにはReact.FormEventを使用します。

const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
  event.preventDefault();
  console.log("Form submitted");
};

const Form: React.FC = () => {
  return (
    <form onSubmit={handleSubmit}>
      <input type="text" name="username" />
      <button type="submit">Submit</button>
    </form>
  );
};

event.preventDefault()を安全に使用できるのは、型がReact.FormEventであるためです。

型情報を活用したカスタムイベント

独自のイベントやデータをハンドラーに渡す場合も型を定義できます。

例:カスタムイベント

以下はカスタムデータを含むイベントハンドラーの例です:

interface Item {
  id: number;
  name: string;
}

const ItemList: React.FC = () => {
  const handleItemClick = (item: Item) => {
    console.log(`Item clicked: ${item.name}`);
  };

  const items: Item[] = [
    { id: 1, name: "Item 1" },
    { id: 2, name: "Item 2" },
  ];

  return (
    <ul>
      {items.map((item) => (
        <li key={item.id} onClick={() => handleItemClick(item)}>
          {item.name}
        </li>
      ))}
    </ul>
  );
};

この例では、カスタムイベントで渡されるデータの型をItemとして定義しています。

型情報を活用するメリット

  1. エラー防止:型チェックにより、誤ったデータの使用を防ぎます。
  2. 補完機能の向上:型情報をエディタが認識し、補完や警告を提供します。
  3. コードの読みやすさ:イベントや状態の意図が明確になり、他の開発者が理解しやすくなります。

型情報を活用することで、React開発における状態管理とイベント処理の信頼性を大幅に向上させることができます。次は、子コンポーネントに型情報を渡すパターン別実装例を紹介します。

子コンポーネントに型情報を渡すパターン別実装例

ReactでTypeScriptを活用する際、子コンポーネントに型情報を渡す方法は柔軟かつ多様です。本節では、実際の開発で役立つ型情報の渡し方をパターン別に紹介します。

1. シンプルなPropsの渡し方

最も基本的なパターンは、親コンポーネントから子コンポーネントに単純なPropsを渡す方法です。

例:基本的な型情報の渡し方

interface GreetingProps {
  name: string;
}

const Greeting: React.FC<GreetingProps> = ({ name }) => {
  return <p>Hello, {name}!</p>;
};

const App: React.FC = () => {
  return <Greeting name="Alice" />;
};

このパターンは、Propsが単純なデータ(文字列や数値)である場合に適しています。


2. 関数を渡すパターン

親コンポーネントから子コンポーネントに関数を渡すことで、イベントを親に通知できます。

例:イベントハンドラーの渡し方

interface ButtonProps {
  onClick: () => void;
}

const Button: React.FC<ButtonProps> = ({ onClick }) => {
  return <button onClick={onClick}>Click me</button>;
};

const App: React.FC = () => {
  const handleClick = () => {
    console.log("Button clicked!");
  };

  return <Button onClick={handleClick} />;
};

この例では、onClickプロパティが親から渡され、子コンポーネントで使用されています。


3. コンポーネントを渡すパターン

親コンポーネントが子コンポーネントに別のコンポーネントを渡すことも可能です。

例:カスタムレンダリング

interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => JSX.Element;
}

const List = <T,>({ items, renderItem }: ListProps<T>) => {
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{renderItem(item)}</li>
      ))}
    </ul>
  );
};

const App: React.FC = () => {
  const users = [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }];

  return (
    <List
      items={users}
      renderItem={(user) => <p>{user.name}</p>}
    />
  );
};

このパターンでは、親コンポーネントが渡すrenderItem関数によって子コンポーネントのレンダリング方法を制御します。


4. コンテキストを使用した型情報の共有

複数の子コンポーネントに共通の型情報を渡す場合、コンテキストを活用するのが便利です。

例:コンテキストでのデータ共有

interface Theme {
  color: string;
}

const ThemeContext = React.createContext<Theme | undefined>(undefined);

const ThemedText: React.FC = () => {
  const theme = React.useContext(ThemeContext);

  if (!theme) throw new Error("ThemeContext is not provided!");

  return <p style={{ color: theme.color }}>This is a themed text!</p>;
};

const App: React.FC = () => {
  const theme = { color: "blue" };

  return (
    <ThemeContext.Provider value={theme}>
      <ThemedText />
    </ThemeContext.Provider>
  );
};

この例では、コンテキストを使用してTheme型の情報を子コンポーネントに渡しています。


5. 高階コンポーネントを使用した型情報の付加

高階コンポーネント(HOC)を使うと、型情報を動的に追加できます。

例:HOCによる型情報の付加

interface WithLoadingProps {
  isLoading: boolean;
}

const withLoading = <P extends object>(
  Component: React.ComponentType<P>
) => {
  const WrappedComponent: React.FC<P & WithLoadingProps> = ({
    isLoading,
    ...props
  }) => {
    return isLoading ? <p>Loading...</p> : <Component {...(props as P)} />;
  };

  return WrappedComponent;
};

const DataDisplay: React.FC<{ data: string }> = ({ data }) => {
  return <p>{data}</p>;
};

const DataDisplayWithLoading = withLoading(DataDisplay);

const App: React.FC = () => {
  return <DataDisplayWithLoading isLoading={false} data="Here is the data!" />;
};

このパターンでは、HOCを利用して型情報を動的に追加しています。


型情報を渡す際のポイント

  1. 型の過剰な複雑化を避ける:必要以上に複雑な型を定義しない。
  2. 共通型の再利用:繰り返し使用される型を共通化する。
  3. ユースケースに応じたパターン選択:必要な柔軟性に応じて適切なパターンを選ぶ。

これらのパターンを理解し適切に活用することで、Reactアプリケーションの型安全性と保守性を向上させることができます。次は、学んだ知識を応用した実践演習として小規模アプリの作成を解説します。

実践演習:型情報を活用した小規模アプリの作成

ここでは、ReactとTypeScriptを活用して、簡単なタスク管理アプリを作成します。この演習を通じて、これまで学んだ型情報の利用方法を実践的に理解できます。

アプリの概要

  • 機能:タスクの追加、一覧表示、完了状態の切り替え。
  • 型情報
  • タスクデータの型を定義。
  • 子コンポーネントへの型情報の適用。
  • 状態管理とイベント処理の型情報を活用。

ステップ1:型情報の定義

タスクデータの型を定義します。

interface Task {
  id: number;
  title: string;
  completed: boolean;
}

ステップ2:親コンポーネントの作成

Appコンポーネントで状態管理と主要な機能を実装します。

import React, { useState } from "react";

const App: React.FC = () => {
  const [tasks, setTasks] = useState<Task[]>([]);
  const [newTask, setNewTask] = useState<string>("");

  const addTask = () => {
    if (!newTask.trim()) return;

    const newTaskItem: Task = {
      id: tasks.length + 1,
      title: newTask,
      completed: false,
    };

    setTasks([...tasks, newTaskItem]);
    setNewTask("");
  };

  const toggleTaskCompletion = (id: number) => {
    setTasks((prevTasks) =>
      prevTasks.map((task) =>
        task.id === id ? { ...task, completed: !task.completed } : task
      )
    );
  };

  return (
    <div>
      <h1>Task Manager</h1>
      <input
        type="text"
        value={newTask}
        onChange={(e) => setNewTask(e.target.value)}
        placeholder="Add a new task"
      />
      <button onClick={addTask}>Add Task</button>
      <TaskList tasks={tasks} onToggleCompletion={toggleTaskCompletion} />
    </div>
  );
};

ステップ3:子コンポーネントの作成

TaskListコンポーネントでタスク一覧を表示します。

interface TaskListProps {
  tasks: Task[];
  onToggleCompletion: (id: number) => void;
}

const TaskList: React.FC<TaskListProps> = ({ tasks, onToggleCompletion }) => {
  return (
    <ul>
      {tasks.map((task) => (
        <li
          key={task.id}
          style={{
            textDecoration: task.completed ? "line-through" : "none",
          }}
        >
          <span>{task.title}</span>
          <button onClick={() => onToggleCompletion(task.id)}>
            {task.completed ? "Undo" : "Complete"}
          </button>
        </li>
      ))}
    </ul>
  );
};

ステップ4:動作確認

このアプリをブラウザで動作させると、以下の機能が確認できます:

  1. タスクを追加すると、リストに表示される。
  2. タスクを「Complete」ボタンで完了状態にできる。
  3. 「Undo」ボタンでタスクを未完了に戻せる。

型情報の活用ポイント

  1. Task型の定義:タスクデータの型を定義することで、データ操作が安全に。
  2. 子コンポーネントへの型情報の渡しTaskListTaskListPropsを使用。
  3. イベントハンドラーの型情報onToggleCompletionで正しい型のイベント関数を明確化。

コード全体

以下は、すべてのコードを統合した例です:

import React, { useState } from "react";

interface Task {
  id: number;
  title: string;
  completed: boolean;
}

interface TaskListProps {
  tasks: Task[];
  onToggleCompletion: (id: number) => void;
}

const TaskList: React.FC<TaskListProps> = ({ tasks, onToggleCompletion }) => {
  return (
    <ul>
      {tasks.map((task) => (
        <li
          key={task.id}
          style={{
            textDecoration: task.completed ? "line-through" : "none",
          }}
        >
          <span>{task.title}</span>
          <button onClick={() => onToggleCompletion(task.id)}>
            {task.completed ? "Undo" : "Complete"}
          </button>
        </li>
      ))}
    </ul>
  );
};

const App: React.FC = () => {
  const [tasks, setTasks] = useState<Task[]>([]);
  const [newTask, setNewTask] = useState<string>("");

  const addTask = () => {
    if (!newTask.trim()) return;

    const newTaskItem: Task = {
      id: tasks.length + 1,
      title: newTask,
      completed: false,
    };

    setTasks([...tasks, newTaskItem]);
    setNewTask("");
  };

  const toggleTaskCompletion = (id: number) => {
    setTasks((prevTasks) =>
      prevTasks.map((task) =>
        task.id === id ? { ...task, completed: !task.completed } : task
      )
    );
  };

  return (
    <div>
      <h1>Task Manager</h1>
      <input
        type="text"
        value={newTask}
        onChange={(e) => setNewTask(e.target.value)}
        placeholder="Add a new task"
      />
      <button onClick={addTask}>Add Task</button>
      <TaskList tasks={tasks} onToggleCompletion={toggleTaskCompletion} />
    </div>
  );
};

export default App;

この実践演習により、型情報を活用した状態管理や子コンポーネントへの型の渡し方について、実用的な知識を得ることができます。次に、この内容を簡潔にまとめます。

まとめ

本記事では、ReactとTypeScriptを組み合わせて子コンポーネントに型情報を渡す方法について解説しました。Propsの型定義から始まり、Union型やOptional型の応用、再利用性を高める設計、状態管理やイベント処理の型付け、さらにパターン別の型情報の渡し方を実践的に紹介しました。最後に、小規模なタスク管理アプリを作成することで、これらの知識を統合的に活用しました。

適切な型情報を用いることで、コードの安全性と保守性が向上し、効率的な開発が可能になります。これを活用して、より堅牢で拡張性の高いReactアプリケーションを構築してください。

コメント

コメントする

目次