⚛️

初学者のためのReactによるToDoアプリ作成手順

に公開

はじめに

この記事はteam411 Advent Calendar 2025の5日目の記事です。
他の投稿された記事はこちらからご覧いただけますよ。
先日はmiozuneさんの「Notionタスク管理の自己流設定まとめ」でした。
Notionくん、タスク整理してくれる便利な子だと油断してると設定に悩むうちに時間泥棒と化します...

おはようございます!team411の湯葉あれまです。

当記事ではteam411でよく採用されているReactを知ってもらうべく、記事を作成しました。

Reactって何!? 何!?

ReactとはMeta社(旧Facebook)が開発した、WebサイトやWebアプリのUIを効率的に構築するためのJavaScriptライブラリ

よくわかんないね。
すなわち、UIが簡単に作れるJavaScriptのアセットや書き方なんです。

ものは試し!!早速作ってみましょう。

下準備をする

nodeを入れよう

Node.jsを入れましょう。
NodeはJavaScriptをPC内で動かすための実行環境です。
Google Chrome などのブラウザの中でも JavaScript は動作しますが、Node.js はそのコアとなるV8 という JavaScript エンジンを使って、PC上でも JavaScript を実行できるようにした環境です。

ここからインストールしてみましょう。
全部nextを押せばinstallにたどり着くので、それやってfinishです。

これをお好きなディレクトリでターミナル実行してください

nodeのバージョン確認
node -v

さすればnodeのバージョンが分かり、ダウンロードされているか確認できます。

npmのバージョン確認
npm -v

これをすると後で使う、npmというパッケージマネージャのバージョンも分かります。

アプリのベース(開発環境)を作ろう

新しいアプリのファイルを置きたいディレクトリに移動し、ターミナルを開いてください。

新しいアプリの作成
npm create vite@latest

今回はViteを使用しています。

いろいろ聞かれますけど、以下のように設定してください

新しいアプリの設定
Need to install the following packages:
create-vite@8.2.0
Ok to proceed? (y) y

> npx
> create-vite
│
◇  Project name:
│  [任意の名前]
│
◇  Select a framework:
│  React
│
◇  Select a variant:
│  TypeScript
│
◇  Use rolldown-vite (Experimental)?:
│  No
│
◇  Install with npm and start now?
│  Yes

ここで新しく作成したディレクトリに移動し

ディレクトリ移動
cd [作成したディレクトリ名]

ランデブしましょう。

ランデブ
npm run dev

こうするとhttp://localhost:◯◯◯◯/が出るかと思います。
これをブラウザで開くと、Viteを用いてパソコン内で作成されたアプリのサーバーに接続され、既存のコードの結果がでてきます。
今の段階では、Viteが仮のアプリを置いてくれています。

そしてアプリのディレクトリを開いて開発開始です!!

開発しましょう。

構造を知ろう

さて、開いたディレクトリの中にApp.tsx,main.tsxがあります。

  • main.tsx は基本的にあまり触らないファイル。Vite や React の機能をつないで、index.html<div id="root">App コンポーネントを差し込む土台になっています。
  • App.tsxがメインページ。今回はここにToDoアプリが作成される。

また、tsxという拡張子ですが、

  • TypeScriptはJavaScriptの拡張版。型定義が必須。(数値なのか?真偽値?文字列?など)
  • jsxはJavaScriptとhtmlが書けるコード。
  • tsxはjsxのTypeScript版

という物になります。

TypeScript の型定義ですが、関数の引数やオブジェクトのプロパティなどで

[変数名]: [変数型]

のように書きます。
(変数そのものを定義するときは const count: number = 0; のように書きます。)

ではまずReactのfunction Appのところを以下のように変えてください。

App.tsx
import './App.css'

function App() {
  return (
    <>     
    </>
  )
}

export default App

この関数の構造を説明します。

  • function内は基本的にTypeScriptで作成されます。
  • return()の()内はhtmlが書けます。
    • ()内にTypeScriptを記述したい場合は{}の中に書きます。

すなわち、

App.tsx
import './App.css'

function App() {
[TypeScript]
  return (
    <>   
    [HTML,{TypeScript}]
    </>
  )
}

export default App

という構造になります。
つまり先程のApp.tsxの変更は、もともと書かれていたコードを削除し、真っ白にしたわけですな。

また、アロー関数という関数の書き方があります。

const 関数名=(引数)=>{返り値}

これは後々読んでいると感覚がわかると思います。

構造を理解したところで、srcディレクトリにcomponentsディレクトリを作成し、header.tsxファイルを作成してください。

ではこのheader.tsxにToDoを書かれたdivを返す関数Headerを書いてみてください。

header.tsx
function Header() {
    return (
        <div>
            ToDo
        </div>
    )
}

こうなります。
ここで1行目を以下のように変更してください。

header.tsx
export default function Header() {

結構大切な話を省きますが、このHeader関数を他のファイルに輸出しています。つまりほかファイルもこの関数が使えるんですね。ただしそれを使うファイルは、輸入importが必要ですが。

さて、作ったHeaderをApp.tsxで使ってみましょう。
App.tsxの先頭に以下を追加してください。

App.tsx
import Header from './components/header'

これでheader.tsxHeader関数をApp.tsxで使えるようになります。

そしてreturnの中に<Header />を追加してみましょう。

App.tsx
import Header from './components/header'
import './App.css'

function App() {
  return (
    <>     
     <Header />
    </>
  )
}

export default App

実際にブラウザで結果を見ましょう。おそらくToDoと書かれているかと思います。

タスクを追加する機能を作ろう

タスク追加なくしてToDoなし。

componentsディレクトリにadd.tsxを作成してください。
さて、Reactの大切な機能としてuseStateがあります。

useState

useStateは状態を管理する機能です。
まずこの機能はReactの用意したアセットのようなものなので、importが必要です。

import { useState } from "react";

そしてuseState定義は

const [現在の値, 値を変える関数] = useState<>(初期値);

例えば、

const [count, setCount] = useState<number>(0);

値を変更するにはsetCount(新しい値)を使えば良いわけですね。

以下の概念も知ってくださいね。

props

親から子へデータを渡す引数。
以下のように使えます。

// importしたファイル
<ChildComponent name="太郎" />

// exportしたファイル
function ChildComponent(props: { name: string }) {
    return <p>{props.name}</p>;
}

これは関数も渡すことができます。あとで紹介。

add.tsxの基本形を書こう

さて、当ファイルのやるべきことを考えてみましょう。基本的に以下の機能があります。

  • タスク名を記入する場所(inputタグ)
  • タスク登録をするボタン(buttonタグ)
  • 何を入力されたか保存する文字列型のuseStateくん

まだbuttonを押すとリストに追加したり、useStateでセット関数を起動する要素はつけません。
まだ説明していない部分もありますが、一旦書いてみましょう

  • useStateの変数inputValue
    • 変数型はstring
    • 初期値は''
  • 変更する関数setInputValue

とします。

add.tsx
import { useState } from "react";

export default function Add() {
    const [inputValue, setInputValue] = useState<string>('');
    
    return(
        <>
            <input type="text" placeholder="Add a new task" />
            <button>Add</button>
        </>
    );
}

はい。一旦こんな感じ?
ではinputの内容をinputValueに記録しましょう。
ここではonChangeを使います。

onChange

htmlの要素(inputなど)の内容が変わったときに実行される関数です。

<input onChange={(e) => setInputValue(e.target.value)} />
  • eは情報を色々纏めたデータ
  • e.target.valueのように詳細をみると「今入力されている文字」が取得できます。
    • (本当はe以外の名前でも問題ないです。)
  • 気になる方はこちら

つまりさっきのコードはinputに文字が入力されるなどすると、その文字をinputValueに入れてくれますし、そのたびにhtmlを書き換えるわけです。

inputとuseStateを連携

inputにはvalueがあります。これはinputの値を定義するものと思ってください。
(本当はだいぶ違うので...難しいですがご参考に

inputValueの値をvalueと一致させるので、
value={inputValue}inputタグに入れる必要がありそうですね。

さあ書いてみましょう

add.tsx
import { useState } from "react";

export default function Add() {
    const [inputValue, setInputValue] = useState<string>('');
    
    return(
        <>
            <input 
                type="text" 
                onChange={(e) => setInputValue(e.target.value)} 
                value={inputValue} 
            />
            <button>Add</button>
        </>
    );
}

タスクリストを保存する。

さて、タスクリストを保管する必要が出てきましたね。
これはApp.tsxに保存する必要があります。
なぜなら後でtaskslist.tsxを作成し、実際にタスク一覧を描画するからです。
これはcomponentディレクトリに作成しますので、両方の親であるApp.tsxに置くのがよろしいでしょう。
また描画するのでuseStateを用います。

保存するタスクリストはオブジェクトの配列にします。
各タスクは「タスク名」と「完了したかどうか」を持つので、以下のような形にします:

{ name: string, completed: boolean }

このオブジェクトの配列を管理するuseStateを定義しましょう:

  • useStateの変数tasks
    • 変数型は{name: string, completed: boolean}[]
    • 初期値は[]
  • 変更する関数setTasks

として追加しましょう。

App.tsx
import Header from './components/header'
import { useState } from 'react'
import './App.css'

function App() {
  const[tasks, setTasks] = useState<{name: string, completed: boolean}[]>([]);

  return (
    <>     
     <Header />
    </>
  )
}

export default App

また、add.tsxには追加ボタンがあり、ここからタスクリストを変更する必要がありますね。

add.tsxにはsetTasks関数とtasksの2つがpropsとして渡されなくてはいけません。

propはTypeScriptでは型定義が必要です。
今回の setTasks では、単純に「新しい配列を受け取る関数」として

setTasks: (tasks: {name: string, completed: boolean}[]) => void

と定義しておきます。

Reactでは、親から渡された複数のデータは、自動的にひとつのオブジェクトにまとめられて子コンポーネントに届きます。これを props と呼ぶわけですね。

props:{tasks: {name: string, completed: boolean}[], setTasks: (tasks: {name: string, completed: boolean}[]) => void}

さてこんな構文があります。

const fruits = ["りんご", "みかん"];
const newFruits = [...fruits, "バナナ"];
// ["りんご", "みかん", "バナナ"]

スプレッド構文と言います。

これらを用いると、

const newTask = { name: inputValue, completed: false };
props.setTasks([...props.tasks, newTask])

新しいタスクはcompleted: false(未完了)として追加されます。

よってadd.tsxに与えるものを与えました。
ではAdd関数内に追加用の関数を作ります。
名前をhandleAddTasksとします。

add.tsx
import { useState } from "react";

type Task = { name: string; completed: boolean };

export default function Add(props:{tasks: Task[], setTasks: (tasks: Task[]) => void}) {
    const [inputValue, setInputValue] = useState<string>('');

    const handleAddTasks=()=>{
        const newTask = { name: inputValue, completed: false };
        props.setTasks([...props.tasks, newTask])
        setInputValue('') //inputのリセット
    }

    return(
        <>
            <input type="text" placeholder="Add a new task" onChange={(e) => setInputValue(e.target.value)} value={inputValue} />
            <button>Add</button>
        </>
    );
}

onClickという関数があり、これはonChangeのようにクリックされると実行してくれます。
これを考えると以下のようになります。

add.tsx
import { useState } from "react";

type Task = { name: string; completed: boolean };

export default function Add(props:{tasks: Task[], setTasks: (tasks: Task[]) => void}) {
    const [inputValue, setInputValue] = useState<string>('');

    const handleAddTasks=()=>{
        const newTask = { name: inputValue, completed: false };
        props.setTasks([...props.tasks, newTask])
        setInputValue('') //inputのリセット
    }

    return(
        <>
            <input type="text" placeholder="Add a new task" onChange={(e) => setInputValue(e.target.value)} value={inputValue} />
            <button onClick={handleAddTasks}>Add</button>
        </>
    );
}

ではApp.tsxにこれをつなげましょう。
propsはオブジェクトとして一つの変数になっていますが、

<Add tasks={tasks} setTasks={setTasks} />

のように渡せます。

タスク一覧を表示する機能を作ろう

さあ、追加したタスクを表示するンゴ。
componentsディレクトリにtaskslist.tsxを作成。

ここではmap関数を使います。

map関数

配列の各要素に対して処理を行い、新しい配列を返す関数です。

配列名.map((任意の変数名)=>処理)

例えば、

const numbers = [1, 2, 3];
const doubled = numbers.map((num) => num * 2);
// [2, 4, 6]

Reactでは、配列をHTMLの要素に変換する事が多いです。

const fruits = ["りんご", "みかん", "バナナ"];
return (
    <ul>
        {fruits.map((fruit, index) => (
            <li key={index}>{fruit}</li>
        ))}
    </ul>
);

indexとは配列の番号です。左端から0,1,2...となります。

taskslist.tsxを書こう

propsとしてtasksを受け取り、それをmapで展開しましょう。
あとはpropsを考えつつ、taskslist.tsxApp.tsxを書いてみてください。

<li> タグは本来 <ul><ol> の中に入れるので、ここでは <ul> で全体を包んでいます。

taskslist.tsx
type Task = { name: string; completed: boolean };

export default function TasksList(props: { tasks: Task[] }) {
    return (
        <ul>
            {props.tasks.map((task, index) => (
                <li key={index}>{task.name}</li>
            ))}
        </ul>
    );
}

オブジェクトなので、taskではなくtask.nameでタスク名を取得する点に注意してね!
さて、これをApp.tsxで使いましょう。

App.tsx
import Header from './components/header'
import Add from './components/add'
import TasksList from './components/taskslist'
import { useState } from 'react'
import './App.css'

type Task = { name: string; completed: boolean };

function App() {
  const[tasks, setTasks] = useState<Task[]>([]);

  return (
    <>     
      <Header />
      <Add tasks={tasks} setTasks={setTasks} />
      <TasksList tasks={tasks} />
    </>
  )
}

export default App

これでタスクの追加と表示ができるようになりました!

ブラウザで確認してみましょう。
入力欄にタスクを入力して「Add」ボタンを押すと、タスクが追加されて一覧に表示されるはずです。

タスクを完了する機能を作ろう

さて、追加はできましたが、それはToDoを名乗るには浅ましいでしょう。
完了ボタンを押すとタスクが消える機能を実装しましょう。

完了機能の仕組みを考えよう

完了機能を作るには何が必要でしょうか?考えてみましょう。

私たちのタスクは{ name: string, completed: boolean }というオブジェクトです。
つまり、各タスクが最初から「完了したかどうか」を持っています!

  1. 完了ボタンを押したらcompletedtrueにする → 該当タスクのcompletedを更新
  2. 完了したタスクは表示しない → completedfalseのものだけ表示
  3. 完了ボタンを各タスクに配置する → taskslist.tsxにボタンを追加

こんな感じで、

[
  { name: "買い物", completed: false },  // 未完了 → 表示する
  { name: "掃除", completed: true },     // 完了! → 表示しない
  { name: "勉強", completed: false }     // 未完了 → 表示する
]

完了処理の関数を作ろう

App.tsxに、タスクを完了にする関数を作成します。

App.tsx
  // 完了処理の関数を追加
  const handleCompleteTask = (index: number) => {
    const newTasks = [...tasks];
    newTasks[index] = { ...newTasks[index], completed: true };
    setTasks(newTasks);
  };

TasksListで未完了のタスクだけ表示しよう

taskslist.tsxを修正、完了ボタンを追加、未完了のタスクだけを表示するようにします。

taskslist.tsx
type Task = { name: string; completed: boolean };

export default function TasksList(props: { 
    tasks: Task[], 
    onComplete: (index: number) => void 
}) {
    return (
        <ul>
            {props.tasks.map((task, index) => (
                !task.completed && (
                    <li key={index}>
                        {task.name}
                        <button onClick={() => props.onComplete(index)}>完了</button>
                    </li>
                )
            ))}
        </ul>
    );
}

完成したApp.tsx

すべてをまとめると、App.tsxは以下

App.tsx
import Header from './components/header'
import Add from './components/add'
import TasksList from './components/taskslist'
import { useState } from 'react'
import './App.css'

type Task = { name: string; completed: boolean };

function App() {
  const[tasks, setTasks] = useState<Task[]>([]);

  const handleCompleteTask = (index: number) => {
    const newTasks = [...tasks];
    newTasks[index] = { ...newTasks[index], completed: true };
    setTasks(newTasks);
  };

  return (
    <>     
      <Header />
      <Add tasks={tasks} setTasks={setTasks} />
      <TasksList tasks={tasks} onComplete={handleCompleteTask} />
    </>
  )
}

export default App

これでタスクの追加・表示・完了ができるToDoアプリが完成しました!!!

ブラウザで動作を確認してみましょう。タスクを追加して、完了ボタンを押すとそのタスクが消えるはずです。

さらにアレンジしてみよう

ここまでくればもうアレンジもできるっしょ。

  • 完了したタスクも別枠で表示する(&&を使ってもいいですが、filterで抽出してもいいですな)
  • 完了を取り消せるようにする(completed: falseに戻す処理を追加してみよう)
  • 期限を設ける({ name: string, completed: boolean, deadline: string }のように拡張してみよう)
  • 優先度をつける({ name: string, completed: boolean, priority: number }のように拡張してみよう)
  • 編集機能を追加する(indexを使って特定のタスクのnameを更新しよう)

オブジェクトにしたことで、プロパティを追加するだけで機能拡張がしやすくなったのも良きです。
ここらへんの考え方はtypes.tsのような型定義ファイルを作成して管理するという形で現代ではよく行われます。
あと、App.cssindex.cssでCSS装飾もできます。ぜひやってみてね!!

最後に

ここまで読んでくれて、本当にありがとうございました。
ぜひ「やってみたよー」と声をかけてくださると本当に嬉しい気持ちになります。

再度の説明となり申し訳ないですが、私達team411は大学生が「自分たちの身の回りの課題」を解決するために、 IT技術を用いてプロダクトを開発する電気通信大学のチームです。
当記事を読んでくれるほどの技術への探求心、そして「おもしろさ」を忘れない、そのような方をteam411は歓迎しております。

研修等も一層の充実を目指して頑張っています。ぜひ初心者の方も入部を!!!
ホームページはこちら!

それではありがとうございました!

Discussion