Reactの基本コンポーネントの作り方

はじめに

Reactは、ユーザーインターフェイスを構築するためのJavaScriptライブラリであり、その中心的な概念が「コンポーネント」です。コンポーネントは、再利用可能なUIの部品であり、独立して存在し、個別に考えることができます。この記事では、Reactにおける基本的なコンポーネントの作り方と、その使い方について解説します。

Reactコンポーネントの種類

Reactでコンポーネントを作成する方法には、主に2つのアプローチがあります:

  1. 関数コンポーネント(Function Components)
  2. クラスコンポーネント(Class Components)

現在のReact開発では、関数コンポーネントとHooksの使用が推奨されていますが、両方の理解が重要です。

関数コンポーネント

関数コンポーネントは最もシンプルなReactコンポーネントで、JSXを返す関数として定義されます。

基本的な関数コンポーネント

import React from 'react';

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

export default Greeting;

アロー関数を使った関数コンポーネント

import React from 'react';

const Greeting = () => {
  return <h1>こんにちは、世界!</h1>;
};

export default Greeting;

関数が単一の式を返す場合は、さらに短く書くこともできます:

import React from 'react';

const Greeting = () => <h1>こんにちは、世界!</h1>;

export default Greeting;

クラスコンポーネント

クラスコンポーネントは、ES6のクラス構文を使用し、React.Componentクラスを継承して定義します。

import React, { Component } from 'react';

class Greeting extends Component {
  render() {
    return <h1>こんにちは、世界!</h1>;
  }
}

export default Greeting;

Props(プロパティ)の使用

コンポーネントは、「props」と呼ばれるパラメータを受け取ることができます。これにより、親コンポーネントから子コンポーネントにデータを渡すことが可能になります。

関数コンポーネントでのprops

import React from 'react';

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

export default Greeting;

分割代入を使用して、より簡潔に記述することもできます:

import React from 'react';

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

export default Greeting;

クラスコンポーネントでのprops

import React, { Component } from 'react';

class Greeting extends Component {
  render() {
    return <h1>こんにちは、{this.props.name}さん!</h1>;
  }
}

export default Greeting;

propsのデフォルト値

コンポーネントには、propsのデフォルト値を設定することができます。

関数コンポーネントでは:

import React from 'react';

function Greeting({ name = 'ゲスト' }) {
  return <h1>こんにちは、{name}さん!</h1>;
}

// または
Greeting.defaultProps = {
  name: 'ゲスト'
};

export default Greeting;

クラスコンポーネントでは:

import React, { Component } from 'react';

class Greeting extends Component {
  static defaultProps = {
    name: 'ゲスト'
  };

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

export default Greeting;

状態(State)の管理

状態(State)は、コンポーネント内部で管理されるデータです。状態が変更されると、コンポーネントが再レンダリングされます。

関数コンポーネントでの状態管理(Hooks使用)

React HooksのuseStateを使用すると、関数コンポーネントでも状態を管理できます。

import React, { useState } from 'react';

function Counter() {
  // countは現在の状態、setCountは状態を更新する関数
  const [count, setCount] = useState(0);

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

export default Counter;

クラスコンポーネントでの状態管理

import React, { Component } from 'react';

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  // クラスプロパティ構文を使用すると、constructorを省略できます
  // state = {
  //   count: 0
  // };

  render() {
    return (
      <div>
        <p>カウント: {this.state.count}</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          増加
        </button>
      </div>
    );
  }
}

export default Counter;

イベント処理

Reactコンポーネントでは、DOM要素と同様にイベントを処理することができます。

関数コンポーネントでのイベント処理

import React, { useState } from 'react';

function NameForm() {
  const [name, setName] = useState('');

  const handleChange = (event) => {
    setName(event.target.value);
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    alert(`こんにちは、${name}さん!`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        名前:
        <input type="text" value={name} onChange={handleChange} />
      </label>
      <button type="submit">送信</button>
    </form>
  );
}

export default NameForm;

クラスコンポーネントでのイベント処理

import React, { Component } from 'react';

class NameForm extends Component {
  constructor(props) {
    super(props);
    this.state = {
      name: ''
    };

    // メソッドをバインドする(アロー関数を使う場合は不要)
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({ name: event.target.value });
  }

  handleSubmit(event) {
    event.preventDefault();
    alert(`こんにちは、${this.state.name}さん!`);
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          名前:
          <input
            type="text"
            value={this.state.name}
            onChange={this.handleChange}
          />
        </label>
        <button type="submit">送信</button>
      </form>
    );
  }
}

export default NameForm;

副作用の処理(useEffect)

関数コンポーネントでは、useEffectフックを使用して副作用(データフェッチング、購読、DOMの直接操作など)を処理します。

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

function WindowWidthTracker() {
  const [windowWidth, setWindowWidth] = useState(window.innerWidth);

  useEffect(() => {
    // ウィンドウサイズ変更時のハンドラ
    const handleResize = () => {
      setWindowWidth(window.innerWidth);
    };

    // イベントリスナーを追加
    window.addEventListener('resize', handleResize);

    // クリーンアップ関数: コンポーネントがアンマウントされる際に実行
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []); // 空の依存配列は、エフェクトがマウント時とアンマウント時にのみ実行されることを意味する

  return (
    <div>
      <p>現在のウィンドウ幅: {windowWidth}px</p>
    </div>
  );
}

export default WindowWidthTracker;

コンポーネントの合成

Reactの強力な機能の一つは、コンポーネントを組み合わせて複雑なUIを構築できることです。

import React from 'react';

// ヘッダーコンポーネント
function Header({ title }) {
  return <header><h1>{title}</h1></header>;
}

// コンテンツコンポーネント
function Content({ text }) {
  return <main><p>{text}</p></main>;
}

// フッターコンポーネント
function Footer({ copyright }) {
  return <footer><p>{copyright}</p></footer>;
}

// 上記を組み合わせたページコンポーネント
function Page() {
  return (
    <div className="page">
      <Header title="Reactの基本" />
      <Content text="これはReactコンポーネントの基本についての記事です。" />
      <Footer copyright="© 2025 React入門" />
    </div>
  );
}

export default Page;

子要素の扱い方(children prop)

Reactでは、childrenプロパティを使用して、コンポーネントの開始タグと終了タグの間に配置された要素にアクセスできます。

import React from 'react';

// コンテナコンポーネント
function Container({ children, backgroundColor = 'white' }) {
  return (
    <div style={{ padding: '20px', backgroundColor }}>
      {children}
    </div>
  );
}

// 使用例
function App() {
  return (
    <Container backgroundColor="lightblue">
      <h1>こんにちは!</h1>
      <p>これはコンテナの中のコンテンツです。</p>
    </Container>
  );
}

export default App;

条件付きレンダリング

Reactでは、条件に基づいて異なるコンテンツをレンダリングすることができます。

import React, { useState } from 'react';

function LoginStatus() {
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  // 条件付きレンダリングの方法1: 三項演算子
  return (
    <div>
      {isLoggedIn ? (
        <p>ようこそ、ユーザーさん!</p>
      ) : (
        <p>ログインしてください。</p>
      )}
      <button onClick={() => setIsLoggedIn(!isLoggedIn)}>
        {isLoggedIn ? 'ログアウト' : 'ログイン'}
      </button>
    </div>
  );

  // 条件付きレンダリングの方法2: 論理AND演算子
  /* return (
    <div>
      {isLoggedIn && <p>ようこそ、ユーザーさん!</p>}
      {!isLoggedIn && <p>ログインしてください。</p>}
      <button onClick={() => setIsLoggedIn(!isLoggedIn)}>
        {isLoggedIn ? 'ログアウト' : 'ログイン'}
      </button>
    </div>
  ); */
}

export default LoginStatus;

リストのレンダリング

配列データをマップして、JSX要素のリストをレンダリングすることができます。

import React from 'react';

function FruitList() {
  const fruits = ['りんご', 'バナナ', 'オレンジ', 'ぶどう', '桃'];

  return (
    <div>
      <h2>フルーツリスト</h2>
      <ul>
        {fruits.map((fruit, index) => (
          <li key={index}>{fruit}</li>
        ))}
      </ul>
    </div>
  );
}

export default FruitList;

注意: 実際のアプリケーションでは、インデックスをキーとして使用することは避け、一意のIDを使用することが推奨されています。

フラグメント

Reactコンポーネントは単一のルート要素を返す必要がありますが、余分なDOM要素を追加したくない場合は、Fragmentを使用できます。

import React, { Fragment } from 'react';

function ArticleSection() {
  return (
    <Fragment>
      <h2>記事のタイトル</h2>
      <p>これは記事の本文の最初の段落です。</p>
      <p>これは記事の本文の2番目の段落です。</p>
    </Fragment>
  );

  // 短縮構文
  /* return (
    <>
      <h2>記事のタイトル</h2>
      <p>これは記事の本文の最初の段落です。</p>
      <p>これは記事の本文の2番目の段落です。</p>
    </>
  ); */
}

export default ArticleSection;

カスタムフック

関数コンポーネントでのロジックの再利用には、カスタムフックを作成することができます。

import { useState, useEffect } from 'react';

// カスタムフック: ウィンドウのサイズを追跡
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 { width, height } = useWindowSize();

  return (
    <div>
      <p>ウィンドウの幅: {width}px</p>
      <p>ウィンドウの高さ: {height}px</p>
      {width < 768 ? (
        <p>モバイルビュー</p>
      ) : (
        <p>デスクトップビュー</p>
      )}
    </div>
  );
}

export default ResponsiveComponent;

実践的なコンポーネント例

最後に、これまで学んだ概念を組み合わせた実践的なコンポーネントの例を見てみましょう。

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

function TodoApp() {
  // 状態の定義
  const [todos, setTodos] = useState([]);
  const [inputValue, setInputValue] = useState('');
  const [filter, setFilter] = useState('all'); // 'all', 'active', 'completed'

  // 入力値の変更ハンドラ
  const handleInputChange = (e) => {
    setInputValue(e.target.value);
  };

  // Todoの追加
  const handleAddTodo = (e) => {
    e.preventDefault();
    if (!inputValue.trim()) return;

    const newTodo = {
      id: Date.now(),
      text: inputValue,
      completed: false
    };

    setTodos([...todos, newTodo]);
    setInputValue('');
  };

  // Todoの完了状態の切り替え
  const handleToggleTodo = (id) => {
    setTodos(
      todos.map(todo =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    );
  };

  // Todoの削除
  const handleDeleteTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };

  // フィルターされたTodoリスト
  const filteredTodos = todos.filter(todo => {
    if (filter === 'active') return !todo.completed;
    if (filter === 'completed') return todo.completed;
    return true; // 'all'の場合
  });

  // 残りのタスク数を計算(副作用の例)
  const [activeTodoCount, setActiveTodoCount] = useState(0);
  useEffect(() => {
    setActiveTodoCount(todos.filter(todo => !todo.completed).length);
  }, [todos]);

  // コンポーネントのレンダリング
  return (
    <div className="todo-app">
      <h1>Todoリスト</h1>
      
      {/* Todoフォーム */}
      <form onSubmit={handleAddTodo}>
        <input
          type="text"
          value={inputValue}
          onChange={handleInputChange}
          placeholder="新しいタスクを入力..."
        />
        <button type="submit">追加</button>
      </form>
      
      {/* フィルター */}
      <div className="filters">
        <button onClick={() => setFilter('all')} disabled={filter === 'all'}>
          すべて
        </button>
        <button onClick={() => setFilter('active')} disabled={filter === 'active'}>
          未完了
        </button>
        <button onClick={() => setFilter('completed')} disabled={filter === 'completed'}>
          完了済み
        </button>
      </div>
      
      {/* Todoリスト */}
      <ul className="todo-list">
        {filteredTodos.length === 0 ? (
          <li className="empty-message">タスクがありません</li>
        ) : (
          filteredTodos.map(todo => (
            <li key={todo.id} className={todo.completed ? 'completed' : ''}>
              <input
                type="checkbox"
                checked={todo.completed}
                onChange={() => handleToggleTodo(todo.id)}
              />
              <span>{todo.text}</span>
              <button onClick={() => handleDeleteTodo(todo.id)}>削除</button>
            </li>
          ))
        )}
      </ul>
      
      {/* タスク数の表示 */}
      <div className="todo-count">
        残りのタスク: {activeTodoCount}
      </div>
    </div>
  );
}

export default TodoApp;

まとめ

Reactコンポーネントの基本的な作り方について解説しました。主なポイントは以下の通りです:

  1. Reactコンポーネントには関数コンポーネントとクラスコンポーネントの2種類があり、現在は関数コンポーネントの使用が推奨されています。
  2. Propsを使用して親コンポーネントから子コンポーネントにデータを渡します。
  3. 状態(State)を使用してコンポーネント内のデータを管理します。関数コンポーネントではuseStateフックを使用します。
  4. イベント処理を行い、ユーザーインタラクションに応答します。
  5. useEffectフックを使用して副作用を管理します。
  6. コンポーネントを組み合わせて、より複雑なUIを構築できます。
  7. childrenプロパティを使用して、コンポーネント内に子要素を配置できます。
  8. 条件付きレンダリングとリストのレンダリングによって、動的なUIを作成できます。
  9. カスタムフックを作成して、ロジックを再利用できます。

これらの概念を理解し実践することで、効率的で保守性の高いReactアプリケーションを構築することができます。Reactの力は、コンポーネントの再利用性と組み合わせの柔軟性にあります。小さなコンポーネントから始めて、それらを組み合わせることで、複雑なユーザーインターフェイスを構築していきましょう。

コメント

タイトルとURLをコピーしました