React学習記録

【React入門】シンプルなTODOアプリの作り方 – 前編【プロジェクト作成〜タスク表示】

yuya_react

はじめに

こんにちは、優也@月収100万を目指して月100時間勉強する男です。

これまで4回に渡って、Reactの基礎知識を学んできました。環境構築、useState、イベントハンドラ、map関数…これらの知識がすべて揃ったので、いよいよ実際にTODOアプリを作っていきます!

前編となる今回は、プロジェクトのセットアップから、タスクの一覧表示までを実装します。まずは動くものを作って、次回以降で機能を追加していく形で進めます。

3回のシリーズで、完全に動作するTODOアプリを完成させましょう!

今回作るもの

前編(今回)で実装する機能

  • ✅ プロジェクトのセットアップ
  • ✅ 初期データの準備
  • ✅ タスク一覧の表示
  • ✅ 未完了/完了タスクの分離表示

中編(次回)で実装する機能

  • タスクの追加機能

後編(次々回)で実装する機能

  • タスクの削除機能
  • タスクの完了機能

プロジェクトの作成

まずは新しいプロジェクトを作成します。

Viteでプロジェクト作成

環境構築の方法を忘れた方は、記事2: Vite環境構築を参照してください。

npm create vite@latest

設定:

  • プロジェクト名: my-todo-app
  • フレームワーク: React
  • バリアント: JavaScript

依存パッケージのインストール

cd my-todo-app
npm install

開発サーバーの起動

bash

npm run dev

ブラウザで http://localhost:5173/ を開いて、Viteのデフォルト画面が表示されればOKです。

不要なファイルを削除

Viteが自動生成したファイルの一部は不要なので削除します。

  • src/App.css
  • src/index.css
  • src/assets/react.svg
  • public/vite.svg

src/App.jsx を空にする

src/App.jsxを以下のコードに書き換えます:

function App() {
  return (
    <div>
      <h1>TODO APP</h1>
    </div>
  );
}

export default App;

src/main.jsx を修正

src/main.jsxから不要なインポートを削除:

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)

ブラウザに「TODO APP」と表示されればOKです!

初期データの準備

まずは表示するための初期データを用意します。

App.jsx にstateを追加

import { useState } from 'react';

function App() {
  const [tasks, setTasks] = useState([
    { id: 1, taskName: '朝食を作る', completed: false },
    { id: 2, taskName: '買い物に行く', completed: false },
    { id: 3, taskName: 'ブログを書く', completed: true }
  ]);

  return (
    <div>
      <h1>TODO APP</h1>
    </div>
  );
}

export default App;

データ構造の説明

各タスクは以下の3つのプロパティを持ちます:

  • id: タスクの一意な識別子(数値)
  • taskName: タスクの名前(文字列)
  • completed: 完了状態(true/false)

この構造で、タスクの管理をシンプルに行えます。

タスク一覧を表示する

map関数を使ってタスクを一覧表示します。

全タスクを表示

import { useState } from 'react';

function App() {
  const [tasks, setTasks] = useState([
    { id: 1, taskName: '朝食を作る', completed: false },
    { id: 2, taskName: '買い物に行く', completed: false },
    { id: 3, taskName: 'ブログを書く', completed: true }
  ]);

  return (
    <div>
      <h1>TODO APP</h1>
      
      <h2>タスク一覧</h2>
      <table border="1">
        <thead>
          <tr>
            <th>タスク名</th>
            <th>状態</th>
          </tr>
        </thead>
        <tbody>
          {tasks.map((task) => (
            <tr key={task.id}>
              <td>{task.taskName}</td>
              <td>{task.completed ? '完了' : '未完了'}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

export default App;

ポイント

  • map関数で配列を繰り返し処理
  • key={task.id}で一意のキーを指定
  • task.completed ? '完了' : '未完了'で状態を表示(三項演算子)

ブラウザを確認すると、3つのタスクが表形式で表示されているはずです!

未完了と完了を分けて表示

実際のTODOアプリでは、未完了タスクと完了タスクを分けて表示したいですね。

filter と map を組み合わせる

import { useState } from 'react';

function App() {
  const [tasks, setTasks] = useState([
    { id: 1, taskName: '朝食を作る', completed: false },
    { id: 2, taskName: '買い物に行く', completed: false },
    { id: 3, taskName: 'ブログを書く', completed: true }
  ]);

  return (
    <div>
      <h1>TODO APP</h1>
      
      <h2>未完了タスク</h2>
      <table border="1">
        <thead>
          <tr>
            <th>タスク名</th>
          </tr>
        </thead>
        <tbody>
          {tasks
            .filter((task) => !task.completed)
            .map((task) => (
              <tr key={task.id}>
                <td>{task.taskName}</td>
              </tr>
            ))}
        </tbody>
      </table>

      <h2>完了タスク</h2>
      <table border="1">
        <thead>
          <tr>
            <th>タスク名</th>
          </tr>
        </thead>
        <tbody>
          {tasks
            .filter((task) => task.completed)
            .map((task) => (
              <tr key={task.id}>
                <td>{task.taskName}</td>
              </tr>
            ))}
        </tbody>
      </table>
    </div>
  );
}

export default App;

コードの説明

未完了タスク:

tasks.filter((task) => !task.completed)

completedfalseのタスクだけを抽出

完了タスク:

tasks.filter((task) => task.completed)

completedtrueのタスクだけを抽出

これで、未完了タスク2件、完了タスク1件が別々に表示されます!

空のリストに対応する

タスクがない場合のメッセージも追加しましょう。

import { useState } from 'react';

function App() {
  const [tasks, setTasks] = useState([
    { id: 1, taskName: '朝食を作る', completed: false },
    { id: 2, taskName: '買い物に行く', completed: false },
    { id: 3, taskName: 'ブログを書く', completed: true }
  ]);

  const incompleteTasks = tasks.filter((task) => !task.completed);
  const completedTasks = tasks.filter((task) => task.completed);

  return (
    <div>
      <h1>TODO APP</h1>
      
      <h2>未完了タスク</h2>
      {incompleteTasks.length === 0 ? (
        <p>未完了タスクはありません</p>
      ) : (
        <table border="1">
          <thead>
            <tr>
              <th>タスク名</th>
            </tr>
          </thead>
          <tbody>
            {incompleteTasks.map((task) => (
              <tr key={task.id}>
                <td>{task.taskName}</td>
              </tr>
            ))}
          </tbody>
        </table>
      )}

      <h2>完了タスク</h2>
      {completedTasks.length === 0 ? (
        <p>完了タスクはありません</p>
      ) : (
        <table border="1">
          <thead>
            <tr>
              <th>タスク名</th>
            </tr>
          </thead>
          <tbody>
            {completedTasks.map((task) => (
              <tr key={task.id}>
                <td>{task.taskName}</td>
              </tr>
            ))}
          </tbody>
        </table>
      )}
    </div>
  );
}

export default App;

ポイント

  • filterの結果を変数に保存(incompleteTasks, completedTasks)
  • length === 0でタスクの有無を判定
  • 三項演算子でメッセージまたはテーブルを表示

実際にtaskの初期値を[]にしてみると…

現在のコード全体

ここまでで完成したコードは以下の通りです:

import { useState } from 'react';

function App() {
  const [tasks, setTasks] = useState([
    { id: 1, taskName: '朝食を作る', completed: false },
    { id: 2, taskName: '買い物に行く', completed: false },
    { id: 3, taskName: 'ブログを書く', completed: true }
  ]);

  const incompleteTasks = tasks.filter((task) => !task.completed);
  const completedTasks = tasks.filter((task) => task.completed);

  return (
    <div>
      <h1>TODO APP</h1>
      
      <h2>未完了タスク</h2>
      {incompleteTasks.length === 0 ? (
        <p>未完了タスクはありません</p>
      ) : (
        <table border="1">
          <thead>
            <tr>
              <th>タスク名</th>
            </tr>
          </thead>
          <tbody>
            {incompleteTasks.map((task) => (
              <tr key={task.id}>
                <td>{task.taskName}</td>
              </tr>
            ))}
          </tbody>
        </table>
      )}

      <h2>完了タスク</h2>
      {completedTasks.length === 0 ? (
        <p>完了タスクはありません</p>
      ) : (
        <table border="1">
          <thead>
            <tr>
              <th>タスク名</th>
            </tr>
          </thead>
          <tbody>
            {completedTasks.map((task) => (
              <tr key={task.id}>
                <td>{task.taskName}</td>
              </tr>
            ))}
          </tbody>
        </table>
      )}
    </div>
  );
}

export default App;

コード行数: 約60行

まとめ

TODOアプリの前編として、タスク一覧の表示機能を実装しました。

今回実装した機能:

  • ✅ プロジェクトのセットアップ
  • ✅ 初期データの準備
  • ✅ タスク一覧の表示
  • ✅ 未完了/完了タスクの分離表示
  • ✅ 空のリスト対応

使った技術:

  • useState: タスクデータの管理
  • map: 配列を繰り返し表示
  • filter: 条件に合うタスクを抽出
  • 三項演算子: 条件分岐

次回の中編では、タスクの追加機能を実装します。入力フォームを作って、新しいタスクを追加できるようにしましょう!


この記事のシリーズ:

  1. 【React入門】未経験からReactを学んで最初のTODOアプリを作った話
  2. 【2025年版】Viteで始めるReact開発環境の構築方法
  3. 【React基礎】useStateとは?初心者向けに分かりやすく解説
  4. 【React】イベントハンドラの書き方完全ガイド
  5. 【React】map関数でリストをレンダリングする方法
  6. 【React入門】TODOアプリの作り方 – 前編 ← 今ここ
  7. 【React入門】TODOアプリの作り方 – 中編(次回)

あとがき

月100時間の学習時間の中で、今月はReactに集中して取り組んでいます。Instagramでは、フリーランスとして月収100万円を目指す道のりを、リアルタイムで発信していきますので、ぜひフォローしてください!

Instagram: @hooded_yuya_100man

ABOUT ME
記事URLをコピーしました