【”超”初心者向け】Reactで作るToDoアプリ|思考プロセスから学ぶ実践ガイド

はじめに

React初心者の皆さん、ようこそ!このガイドでは「ToDoアプリ」を題材に、ReactとTypeScriptを使った開発の流れを完全初心者向けに解説します。

単にコードを書くだけでなく、思考プロセス(どのようにアプリを設計し実装するか)記法のポイント(React/TypeScriptの書き方)の両面からじっくり丁寧に説明するのが本記事の特徴です。

コードを書き写しながら進めることで、コンポーネントやステート、イベントハンドリング、Props(プロパティ)といったReactの基本概念を一つ一つ理解できるようになっています。

他のチュートリアルでは手順だけ追いがちですが、ここでは「なぜその設計にしたか」「このコードは何をしているか」を掘り下げます。

初めてReactに触れる方でも、単なる写経に終わらず考え方書き方の両方が身につくようサポートします。

最後まで進めれば、あなたも自分で簡単なアプリを作れる自信がつくでしょう!

アプリの概要と準備

まず、今回作るToDoアプリの機能と完成イメージを確認しましょう。

ToDoアプリとは、やることリスト(ToDoリスト)を管理するシンプルなアプリです。

このアプリではテキスト入力欄と「追加」ボタンで新しいタスクを追加し、リスト表示された各タスクの横に「削除」ボタンがあり、クリックするとそのタスクを削除できます。

シンプルな機能ですが、コンポーネント分割やデータの受け渡しなどReactの基本を学ぶには十分な題材です。

開発環境の準備(本記事を読むだけでもいいのでここは任意で)

ReactとTypeScriptのプロジェクトを作成するには、Node.jsとnpmがインストールされている必要があります。

環境が整っていれば、Create React App や Vite といったツールで素早く雛形を作れます。

例えばCreate React Appを使う場合、ターミナルで以下のコマンドを実行するとTypeScript対応の新規Reactアプリが作成されます。

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

プロジェクト作成後、エディタでプロジェクトを開いてください(上記コマンドではmy-todo-appというフォルダが作成されます)。

以降は主にsrcフォルダ内のファイルを編集していきます。準備ができたら、早速コンポーネントの構成と設計を考えてみましょう。

Viteで開発環境を構築したい場合は「Reactを5分で試せる!初心者向けReact 開発環境構築ガイド」に詳細な手順を説明しています

思考プロセス:コンポーネント設計とデータ管理

React開発ではまずコンポーネント(部品)ごとにアプリを分割して考えることが重要です。

今回のToDoアプリは非常にシンプルなので、次の2つのコンポーネントだけで構成してみます。

  • Appコンポーネント – アプリ全体を管理する親コンポーネントです。追加フォームとToDoリストの両方を含みます。タスクの一覧データや入力中のテキストなど状態(state)を保持し、子コンポーネントへ渡すデータや関数もここで用意します。
  • TodoListコンポーネント – タスク一覧を表示する子コンポーネントです。親からProps(プロパティ)として受け取ったタスク配列を表示し、各タスクに対する「削除」ボタンの操作を提供します。

本来は入力フォーム部分も独立したAddTodoコンポーネントに分ける設計も考えられますが、初心者向け記事のためここではフォームはAppコンポーネント内に実装します。まずは親子2つのコンポーネントでシンプルにデータの受け渡しを理解しましょう。

データの流れと役割分担

上記のようにコンポーネントを分けたら、データの流れを整理します。

Reactでは通常、状態(state)は最小限の必要な親コンポーネントに持たせ、子には必要なものだけをPropsとして渡す設計をします。

今回のアプリでは以下のデータと関数が登場します。

  • タスク一覧データ:複数のToDo項目を配列で保持します。追加や削除で変化するので、Appコンポーネントのstateとして持ちます。
  • 新規入力中のテキスト:フォームの入力値です。これもApp内のstateとして持ち、フォームの入力フィールドと同期させます(いわゆる制御されたコンポーネントとします)。
  • タスク追加関数:入力されたテキストを使って新しいタスク項目をstateに追加する関数です。App内で定義し、ボタンのonClickイベントで使います。
  • タスク削除関数:指定したタスクを一覧から削除する関数です。これもApp内で定義し、TodoListにProps経由で渡して子コンポーネント内で呼び出します(子から親への処理呼び出しを実現)。

このように「どのデータをどこで持つか」「どのコンポーネントで処理するか」を最初に決めておくと、実装がスムーズになります。

特にReactでは、親->子へデータや関数をPropsで渡すのが基本的な流れです(子から親へ直接データを渡すことはできないため、親から渡したコールバック関数を子が呼ぶ形で親のstateを更新します)。

props解説参考リンク:ja.react.dev

では、以上の設計を踏まえて実際にコードを書いていきましょう。まずは親であるAppコンポーネントの実装から始めます。

App.tsx – アプリ全体のコンポーネント

以下はsrc/App.tsxのコード全文です。TypeScriptで記述しており、Reactの関数コンポーネントとして定義しています。

ファイルごとにコードを示した後で、各部分を抜粋しながら詳細に解説していきます。

import React, { useState } from 'react';
import TodoList from './TodoList';  // 子コンポーネントのインポート

function App() {
  // ToDo項目の型定義
  type Todo = {
    id: number;
    text: string;
  };

  // 複数のToDo項目を状態として管理
  const [todos, setTodos] = useState<Todo[]>([
    { id: 1, text: '最初のタスク' }
  ]);
  // 入力中のテキストを状態として管理
  const [inputText, setInputText] = useState('');

  // 新しいToDoを追加する関数
  const handleAddTodo = () => {
    const trimmedText = inputText.trim();
    if (trimmedText === '') return; // 空文字の場合は何もしない
    const newTodo: Todo = {
      id: Date.now(),       // 一意なIDを現在時刻から生成
      text: trimmedText
    };
    setTodos([...todos, newTodo]);  // 新しい配列を作ってstate更新
    setInputText('');               // テキストボックスを空にリセット
  };

  // 指定したIDのToDoを削除する関数
  const handleDeleteTodo = (id: number) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };

  return (
    <div>
      <h1>ToDoアプリ</h1>
      {/* 新規タスク追加フォーム */}
      <div>
        <input 
          type="text"
          value={inputText}
          onChange={e => setInputText(e.target.value)}
          placeholder="タスクを入力"
        />
        <button onClick={handleAddTodo}>追加</button>
      </div>

      {/* ToDoリスト表示 */}
      <TodoList 
        todos={todos} 
        onDelete={handleDeleteTodo} 
      />
    </div>
  );
}

export default App;

App.tsx のコード解説|親コンポーネントの役割を理解しよう

まずはインポートと関数定義から

App.tsx は、アプリの中心となる親コンポーネントです。ファイルの冒頭で必要なモジュールをインポートしています。

import React, { useState } from 'react';
import { TodoList } from './TodoList';
  • useStateReactのフック(Hook) のひとつで、状態管理に使います。
  • TodoList は後ほど解説する 子コンポーネント です。

React 17以降、JSXを使うだけなら import React from 'react'; は省略可能ですが、互換性のために明示しておくと安心です。

関数コンポーネントとして function App() を定義し、最後に return でUIを返す構造になっています。


Todo型の定義と state(状態)の管理

Todo 型を定義しておこう

type Todo = {
id: number;
text: string;
};

この型は、ToDoアプリで扱う1件のタスクのデータ構造を表します。

  • id: 一意に識別するための数値
  • text: タスクの内容(文字列)

このように TypeScriptの type 構文 を使って型定義を行うことで、後続のコードで型のチェックが効くようになります。


useState で Todo一覧を管理

const [todos, setTodos] = useState<Todo[]>([
{ id: 1, text: '最初のタスク' }
]);

ここで todos というステートを宣言しています。

  • useState<Todo[]><Todo[]> は、配列の中身が Todo 型であることを明示する ジェネリクス構文
  • setTodostodos を更新するための関数です。

Reactでは、ステートが変更されると自動で画面が再描画(再レンダリング)されます。


入力欄の状態もステートで管理する

const [inputText, setInputText] = useState('');

こちらはテキスト入力欄に対応するステートです。

  • inputText: 入力欄の中身(文字列)
  • setInputText: それを更新する関数

フォームの値を state で管理することで、リアルタイムに入力内容を追跡できる「制御されたコンポーネント」になります。


新しいタスクを追加する:handleAddTodo関数

const handleAddTodo = () => {
const trimmedText = inputText.trim();
if (trimmedText === '') return;

const newTodo: Todo = {
id: Date.now(),
text: trimmedText
};

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

この関数は、ユーザーが「追加」ボタンを押したときに実行されます。

処理の流れを順に見てみましょう

1. 空白を除去する

const trimmedText = inputText.trim();

文字列の両端の空白を削除します。これにより、スペースだけの入力を防げます。

2. 空チェック

if (trimmedText === '') return;

空の入力だったら、関数を即終了します。何も追加しないようにガードしています。

3. 新しいタスクの作成

const newTodo: Todo = {
id: Date.now(),
text: trimmedText
};
  • idDate.now() で一意な値に
  • text はユーザーの入力内容(空白除去済み)

4. 配列に追加する

setTodos([...todos, newTodo]);

スプレッド構文(...todos)で 既存の配列をコピーしつつ、新しい要素を追加します。

Reactのステートは直接変更せず、新しい配列やオブジェクトを作って更新するのが原則です。

5. 入力欄をリセット

setInputText('');

フォームを空に戻して、次の入力を受け付けやすくします。


タスクを削除する:handleDeleteTodo関数

const handleDeleteTodo = (id: number) => {
setTodos(todos.filter(todo => todo.id !== id));
};

この関数は、指定した id を持つタスクを削除します。

  • filter() は配列から 条件に合う要素だけを残す関数
  • todo.id !== id で「指定IDと異なるものだけ」を残しています

結果的に、該当タスクが一覧から削除され、画面も即座に更新されます。


JSXによるUIレンダリング

JSXとは?

JSX(ジーエスエックス)とは、JavaScriptの中にHTMLライクなタグを書ける構文です。

Reactコンポーネントの見た目(UI)を宣言的に表現できます。


App コンポーネントの return 文

return (
<div>
<h1>ToDoアプリ</h1>

{/* 新規タスク追加フォーム */}
<div>
<input
type="text"
value={inputText}
onChange={e => setInputText(e.target.value)}
placeholder="タスクを入力"
/>
<button onClick={handleAddTodo}>追加</button>
</div>

{/* ToDoリスト表示 */}
<TodoList
todos={todos}
onDelete={handleDeleteTodo}
/>
</div>
);

JSXの書き方のポイント

<h1> で見出しを表示

<h1>ToDoアプリ</h1>

単純なテキストの表示には通常のHTMLタグと同様に記述できます。

② 入力フォームとボタン

<input 
type="text"
value={inputText}
onChange={e => setInputText(e.target.value)}
placeholder="タスクを入力"
/>
  • value={inputText}現在のステートと同期
  • onChange={...}入力のたびにステート更新
<button onClick={handleAddTodo}>追加</button>
  • onClick は HTML と違い キャメルケース表記 に注意!

子コンポーネント TodoList を呼び出す

<TodoList 
todos={todos}
onDelete={handleDeleteTodo}
/>
  • todos: タスク一覧のデータ
  • onDelete: 削除用のコールバック関数

このように、Props(親→子へのデータ受け渡し) により、Appコンポーネントが持つ情報を TodoList に渡すことができます。

App.tsx まとめ

このファイルでは、以下のReactの基本パターンを実装しました

  • useState によるステート管理(配列+文字列)
  • 型定義(Todo型)の活用
  • イベントハンドラによる追加・削除処理
  • 子コンポーネントへのPropsの受け渡し
  • JSXによるUI構築と制御されたフォームの実装

シンプルながら、Reactアプリの土台を構成する要素が詰まっています。

ステートとイベントの連携、コンポーネントの分離、Props設計はすべてのReact開発に通じる重要な考え方です。

TodoList.tsx – タスク一覧部分のコンポーネント

ToDoリストの一覧表示は、TodoList.tsxという子コンポーネントに分離して記述します。

親である App.tsx から渡された「タスクの一覧」と「削除処理関数」を Props 経由で受け取り、それを元にリストを描画するシンプルな構成です。

以下がその全コードです。

import React from 'react';

// Propsの型定義
type Todo = {
id: number;
text: string;
};

type Props = {
todos: Todo[];
onDelete: (id: number) => void;
};

function TodoList({ todos, onDelete }: Props) {
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
{todo.text}{" "}
<button onClick={() => onDelete(todo.id)}>削除</button>
</li>
))}
</ul>
);
}

export default TodoList;

TodoList.tsx のコード解説|子コンポーネントの役割を理解しよう

Props(プロップス)の型定義

まずは type Todotype Props の定義から見ていきましょう。

type Todo = {
id: number;
text: string;
};

この Todo 型は「1つのタスクが持つべきデータ構造」を定義しています。

  • id: 一意な数値識別子(ミリ秒など)
  • text: タスクの内容(文字列)

App.tsx でも同じ型が定義されていますが、ここでは説明の都合上、同じ内容を再定義しています(本来は共通ファイルに切り出すのがベストです)。

次に、Propsの型を定義します。

type Props = {
todos: Todo[];
onDelete: (id: number) => void;
};

ここでは、親から受け取る2つの値を明示的に型注釈しています。

  • todos: Todo 型の配列(リスト表示用のタスク一覧)
  • onDelete: タスク削除時に呼び出す関数(引数はタスクのID)

onDelete の型 (id: number) => void について補足しますと

  • id: number … 削除したいタスクのIDを引数に取る
  • => void … 返り値は特になし(副作用のみの処理)

TypeScriptで「関数の型」はこのように記述します。


コンポーネント関数の定義

関数コンポーネント本体の定義はこちらです。

function TodoList({ todos, onDelete }: Props) {
...
}

ここでは Props 型の引数 props を、分割代入を使って展開しています。

  • { todos, onDelete }: propsオブジェクトから直接必要な値だけを取り出す書き方
  • : Props: その props が Props 型であることを明示

この分割代入により、関数内で props.todosprops.onDelete と書かずに、いきなり todosonDelete として扱えるためコードがシンプルになります。

補足として、以下のように書くことも可能です。

function TodoList(props: Props) {
return <ul>{props.todos.map(...)}</ul>;
}

ですが、Reactでは可読性の面からも分割代入が一般的に推奨されています。


JSXでのリスト描画

次に、関数の return 部分を見てみましょう。

return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
{todo.text}{" "}
<button onClick={() => onDelete(todo.id)}>削除</button>
</li>
))}
</ul>
);

<ul><li> の構造

  • <ul> … HTMLの「順序なしリスト」タグ。複数の項目を箇条書き表示する際に使います。
  • <li> … 各リスト項目を表します。

ここでは todos.map(...) を使って、ToDo配列の中身を一つずつ <li> に変換しています。


map関数による繰り返し処理

todos.map(todo => (
<li key={todo.id}>...</li>
))
  • map() は配列をループして、新しい要素(ここでは <li>)の配列を生成するメソッドです。
  • JSX内でも map() を使ってUIを反復的に描画できます。

key属性の指定

<li key={todo.id}>

key 属性は、Reactが各要素を一意に識別するために必須の属性です。

  • key を指定しないと、更新や削除が正しく反映されない場合があります。
  • todo.id のように「その要素だけが持つユニークなID」を指定します。

削除ボタンとイベント処理

<button onClick={() => onDelete(todo.id)}>削除</button>

この部分が、各タスクの「削除ボタン」です。

  • onClick={...} はクリック時のイベントハンドラです。
  • onDelete(todo.id) を直接書いてしまうと即時実行されてしまうため、無名関数でラップしています: tsxコピーする編集する() => onDelete(todo.id)

この書き方により、「クリックされたときにだけ」関数が実行されるようになります。


テキストとスペースの補足

{todo.text}{" "}
  • {todo.text} … 各タスクのテキストを表示
  • {" "} … JSX内で明示的なスペースを入れる小技(改行やインデントでは空白が入らないため)

TodoList.tsx まとめ

このファイルでは、以下のReactの重要な要素を扱いました

  • Propsの型定義と受け取り
  • 配列のmapでリスト描画
  • key属性の活用
  • 関数のイベントバインド(onClick)

シンプルな構成ですが、実際の開発でも頻出するテクニックが凝縮されています。

Props設計とmapの活用は、どんなUIにも応用できる基本です。

動作確認

ここまでで、App.tsxTodoList.tsxの実装ができました。開発サーバーを起動してブラウザで確認してみましょう

開発サーバーを起動しよう

作成したToDoアプリをブラウザで動かすには、以下のコマンドを実行します。

  • Create React Appで作った場合
npm start
  • Viteで作った場合
npm run dev

コマンドを実行すると、ブラウザが自動的に開き、ToDoアプリの画面が表示されるはずです。

アプリの挙動

正しく実装できていれば、ブラウザ上に簡素なToDoリストの画面が表示されるはずです。

入力欄に適当なタスク名を入力して「追加」ボタンを押すとリストに項目が増えます。

各項目の「削除」ボタンを押せばその項目がリストから消えることを確認してください。

本記事で学んだポイント

初心者の方にとっては、一つのアプリを通じてReactの基本概念を学べたことと思います。最後に、本記事で学んだポイントを簡単に振り返りましょう。

コンポーネント思考と設計

アプリを機能ごとにコンポーネントに分割し、データの流れ(stateを持つ箇所とPropsで渡す関係)を考えることが重要です。

今回は親(App)が状態とロジックを持ち、子(TodoList)が表示と一部イベントを担当する形にしました。

状態管理とHooks

useStateフックでコンポーネント内にstateを持たせました。配列や文字列など様々な型のstateを持てます。

stateを更新すると自動でUIが再描画されるため、直接DOMを操作する必要がなく、宣言的にUIを制御できました。

Propsとデータ受け渡し

Propsは親から子へのデータ渡しの仕組みでした。今回は配列や関数をPropsとして渡し、子コンポーネント側で受け取って利用しました。

関数コンポーネントの引数で分割代入する書き方(function Child({ propA, propB }: Props) { ... })も体験しました。TypeScriptのおかげで、受け渡しの型不整合も事前に防げます。

イベント処理

ボタンのonClickや入力欄のonChangeに対して、イベントハンドラ関数(コールバック)を指定しました。

イベントオブジェクトeから値を取り出したり、親から渡された関数を呼び出したりすることで、ユーザーの操作に応じた動的な振る舞いを実装できました。特に子→親方向のデータ伝達は直接できないため、親から渡したコールバックを子で呼ぶ手法を取りました。

リストレンダリングとKey

配列データから動的にJSX要素を生成する方法としてArray.mapを使いました。

Reactでリストを表示する際はkey属性を付けることが重要で、今回のようにユニークなidがある場合はそれをキーに指定するのが定石です。
(参考リンク:a.legacy.reactjs.org

まとめ

以上、「思考プロセスから学ぶ」ことを意識してReact+TypeScriptによるToDoアプリ開発の流れを解説しました。

最初は覚えることが多く感じるかもしれませんが、小さなアプリを作ってみるとReactの開発サイクルが掴めてきたのではないでしょうか。

慣れてきたら、今回作ったToDoアプリに機能を拡張してみるのも良い練習になります。

例えば、タスクに完了状態(チェックボックス)を付けて完了・未完了を切り替えられるようにしたり、複数のコンポーネントに分けてリファクタリングしてみたりすると理解が深まります。

最後までお読みいただきありがとうございます。今回のガイドが、React入門の助けとなり実践的なスキル習得につながれば幸いです。

ぜひ自身でも手を動かして、Reactでの開発を楽しんでください!!

関連記事

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です