📝

初心者のためのReact関数コンポーネント

に公開

はじめに

モダンなWebフロントエンド開発において、Reactは最も人気のあるライブラリの一つです。Reactの大きな特徴はコンポーネント指向であり、UIを再利用可能な部品に分割して開発することができます。
Reactコンポーネントには主に2種類あります:クラスコンポーネントと関数コンポーネントです。以前はクラスコンポーネントが主流でしたが、React 16.8でHooksが導入されて以降、関数コンポーネントが推奨されるようになりました。
この記事では、React初心者の方に向けて、関数コンポーネントの基本から実践的な使い方までを解説します。TypeScriptを使った例も紹介していきますので、モダンなReact開発の第一歩として参考にしてください。

Reactコンポーネントの基本

そもそもコンポーネントとは?

Reactにおいて、コンポーネントとはUIの独立した部品のことです。Reactコンポーネントは、画面に表示されるべきものを返すJavaScript関数です。これらのコンポーネントを組み合わせることで、複雑なUIを構築していきます。

####関数コンポーネントの基本構文
関数コンポーネントは、以下のように単純なJavaScript関数として定義します:

function Greeting() {
  return <h1>こんにちは!</h1>;
}

この例では、Greetingという関数コンポーネントが定義されています。この関数はJSX(JavaScript XML)と呼ばれる構文を返しています。JSXはHTMLに似ていますが、JavaScriptの中でHTMLのようなマークアップを書くことができる特殊な構文です。

関数コンポーネントの特徴

関数コンポーネントには以下のような特徴があります:

  1. シンプルな構文: クラスコンポーネントと比べてコードが簡潔になります
  2. Hooks対応: useState、useEffectなどのHooksを使ってステートや副作用を管理できます
  3. パフォーマンス: 一般的にクラスコンポーネントよりも軽量です
  4. テスト容易性: 純粋な関数なのでテストが書きやすいです

Reactのフックの革新的な導入により、関数コンポーネントの可能性が大きく広がりました。これにより、以前はクラスコンポーネントに限定されていた状態管理やライフサイクル機能が関数コンポーネントでも利用できるようになり、コンポーネントのロジックとコード再利用性が大幅に向上しました。

Props:コンポーネント間のデータ受け渡し

関数コンポーネントは「props」と呼ばれるオブジェクトを受け取ることができます。propsを使うことで親コンポーネントから子コンポーネントへデータを渡すことができます。

基本的なpropsの使い方

function Greeting(props) {
  return <h1>こんにちは、{props.name}さん!</h1>;
}

// 使用例
<Greeting name="田中" />

TypeScriptでpropsに型をつける

TypeScriptを使うとpropsに型を定義できるため、より安全なコードを書くことができます:

type GreetingProps = {
  name: string;
};

function Greeting({ name }: GreetingProps) {
  return <h1>こんにちは、{name}さん!</h1>;
}

TypeScriptを使うと、propsやstateに型を定義することができます。interfaceを定義し、useStateフックには型パラメータを使用することで型安全なコンポーネントを作成できます。

Hooksを使った状態管理

Hooksは関数コンポーネント内で状態やその他のReact機能を「フック」するための関数です。最も基本的なHookはuseStateuseEffectです。

useState:状態の管理

useStateを使うと、関数コンポーネント内で状態(state)を扱えるようになります:

import { useState } from 'react';

function Counter() {
  // countは現在の状態、setCountは状態を更新する関数
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>現在のカウント: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        増加
      </button>
    </div>
  );
}

useStateからは、現在の状態(count)と、それを更新する関数(setCount)の2つが取得できます。

useEffect:副作用の処理

useEffectは、コンポーネントのレンダリング後に実行される副作用を扱うためのHookです:

import { useState, useEffect } from 'react';

function Timer() {
  const [seconds, setSeconds] = useState(0);
  
  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(s => s + 1);
    }, 1000);
    
    // クリーンアップ関数(コンポーネントがアンマウントされる時に実行)
    return () => clearInterval(interval);
  }, []); // 空の依存配列は、このエフェクトがマウント時に1回だけ実行されることを意味します
  
  return <p>経過時間: {seconds}</p>;
}

Hooksの基本ルール

Hooksを使う際には、以下の重要なルールがあります:

  1. ループ、条件分岐、ネストされた関数の中でHooksを呼び出さないでください。代わりに、常にReact関数のトップレベルで、早期リターンの前にHooksを使用してください。
  2. 通常のJavaScript関数からHooksを呼び出さないでください。 Hooksは関数コンポーネント内かカスタムHook内でのみ使用します。

これらのルールを守ることで、コンポーネントが再レンダリングされても状態が正しく保持されます。

カスタムHookの作成

複数のコンポーネントで同じロジックを再利用したい場合は、カスタムHookを作成することができます。カスタムHookは「use」で始まる名前の関数で、内部で他のHooksを使用することができます。

import { useState, useEffect } from 'react';

// ウィンドウのサイズを監視するカスタムHook
function useWindowSize() {
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight,
  });
  
  useEffect(() => {
    const handleResize = () => {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    };
    
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);
  
  return windowSize;
}

// 使用例
function ResponsiveComponent() {
  const size = useWindowSize();
  
  return (
    <div>
      <p>ウィンドウの幅: {size.width}px</p>
      <p>ウィンドウの高さ: {size.height}px</p>
    </div>
  );
}

カスタムHookを作ることで、コンポーネントからロジックを抽出して再利用可能な関数にすることができます。これにより、コンポーネントのコードが読みやすくなり、ロジックの重複を避けることができます。

実践的な関数コンポーネントの例

以下は、関数コンポーネント、props、Hooksを組み合わせた実践的な例です:

import React, { useState, useEffect } from 'react';

// 型定義
type UserData = {
  id: number;
  name: string;
  email: string;
};

type UserListProps = {
  title: string;
  initialUsers?: UserData[];
};

// ユーザーリストコンポーネント
function UserList({ title, initialUsers = [] }: UserListProps) {
  // 状態
  const [users, setUsers] = useState<UserData[]>(initialUsers);
  const [isLoading, setIsLoading] = useState<boolean>(initialUsers.length === 0);
  const [error, setError] = useState<string | null>(null);

  // データ取得用エフェクト
  useEffect(() => {
    if (initialUsers.length > 0) {
      return; // 初期データがある場合は何もしない
    }

    const fetchUsers = async () => {
      try {
        setIsLoading(true);
        const response = await fetch('https://jsonplaceholder.typicode.com/users');
        if (!response.ok) {
          throw new Error('データの取得に失敗しました');
        }
        const data = await response.json();
        setUsers(data);
      } catch (err) {
        setError(err instanceof Error ? err.message : '不明なエラーが発生しました');
      } finally {
        setIsLoading(false);
      }
    };

    fetchUsers();
  }, [initialUsers]);

  // ユーザー削除処理
  const handleDeleteUser = (id: number) => {
    setUsers(users.filter(user => user.id !== id));
  };

  // ローディング中表示
  if (isLoading) {
    return <div>データを読み込み中...</div>;
  }

  // エラー表示
  if (error) {
    return <div>エラー: {error}</div>;
  }

  // ユーザーリスト表示
  return (
    <div>
      <h2>{title}</h2>
      {users.length === 0 ? (
        <p>ユーザーがいません</p>
      ) : (
        <ul>
          {users.map(user => (
            <li key={user.id}>
              <strong>{user.name}</strong> ({user.email})
              <button onClick={() => handleDeleteUser(user.id)}>削除</button>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

export default UserList;

この例では以下の要素を組み合わせています:

  1. TypeScriptでの型定義(UserData、UserListProps)
  2. useState Hookでの複数の状態管理
  3. useEffect Hookでのデータ取得
  4. 条件付きレンダリング
  5. イベントハンドラの実装
  6. リストのレンダリングと適切なkeyの使用

TypeScriptとReact関数コンポーネントのベストプラクティス

TypeScriptを使ったReact関数コンポーネント開発では、以下のベストプラクティスを意識すると良いでしょう:

  1. Propsの型定義: インターフェースまたは型エイリアスを使用してpropsの型を明確に定義する
  2. useState with TypeScript: 複雑な型の場合は明示的に型パラメータを指定する
const [user, setUser] = useState<User | null>(null);
  1. 関数コンポーネントの戻り値型: 必要に応じてJSX.Elementを指定する
const Button = ({ text }: ButtonProps): JSX.Element => {
  return <button>{text}</button>;
};
  1. イベントハンドラの型: React.MouseEventなどの適切なイベント型を使用する
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
  // イベント処理
};

TypeScriptでコンポーネントのpropsとstateを正確に型付けすることで、コンポーネントの信頼性と保守性が向上します。Hooksと一緒にTypeScriptを利用すると、よりクリーンで安全なコードを書くことができます。

まとめ

React関数コンポーネントは、現代のReactアプリケーション開発の中心的な要素です。この記事では以下の内容を学びました:

関数コンポーネントの基本構文と特徴
Propsを使ったコンポーネント間のデータ受け渡し
useState、useEffectなどのHooksを使った状態管理と副作用処理
カスタムHookの作成と再利用可能なロジックの抽出
TypeScriptとの組み合わせによる型安全なコンポーネント開発

関数コンポーネントとHooksを使いこなすことで、より簡潔で保守性の高いReactアプリケーションを構築することができます。次のステップとしては、useReducer、useContext、useMemoなどの他のHooksについても学習することをお勧めします。

Discussion