1. はじめに
今回は、Reactを使ってシンプルなToDoアプリを作ってみます!
「状態管理(useState)」や「イベント処理」「リストの描画(map)」など、Reactの超基本がギュッと詰まった内容なので、初心者の方にピッタリです。
※もしカウンターアプリを作ったことがある方は、そこから少しステップアップした内容になります!
2. プロジェクトの準備
まずは、Reactのプロジェクトを作成しましょう。
ターミナル(コマンドプロンプト)を開いて、作業したいフォルダ(例:デスクトップなど)で以下のコマンドを実行します。
npm create vite@latest
プロジェクト名の入力を求められたら、todo-app-react
と入力してエンターキーを押してください。(プロジェクト名は自由に決めてもok)
その後、以下のコマンドでプロジェクトのフォルダに移動して、必要な依存をインストールします。
cd todo-app-react
npm install
npm run dev
ブラウザで http://localhost:5173
を開いて、「Vite + React」と表示されていれば準備OKです!
CSSの準備
デフォルトで用意されているCSS(App.css
とindex.css
)は今回使わないので、中身をすべて削除しておきましょう。
App.jsxの初期化
次に、src/App.jsx
を開いて、以下のように中身を変更してください!
const App = () => {
return <div>App</div>;
};
export default App;
これで、Reactのアプリの土台が完成しました!
3. UIを作る
準備ができたら、いよいよアプリを作っていきましょう!
まずはUI(見た目の部分)から取りかかります。
App.jsx を以下のように書き換えてください👇
ここでは特に新しいReactの要素は出てこないので、サクッと進めちゃいます!
import "./App.css";
const App = () => {
return (
<div>
<header>
<h1>ToDoアプリ</h1>
</header>
<main>
<div className="list">
<div className="list-header">
<p className="list-title">大学</p>
</div>
<div className="cards-container">
<div className="card">
<div className="todo">これ</div>
<div className="delete"></div>
</div>
<div className="card">
<div className="todo">それ</div>
<div className="delete"></div>
</div>
<div className="card">
<div className="todo">あれ</div>
<div className="delete"></div>
</div>
</div>
<div className="list-footer">
<input
type="text"
className="add-todo"
placeholder="タスクを追加"
/>
<button className="add-button">追加</button>
</div>
</div>
</main>
</div>
);
};
export default App;
HTML部分がちょっと長めですが、今のところはただの見た目なので気楽に!
ここで一つポイント💡
Reactでは、HTMLのクラス指定に class は使えません。
代わりに className と書く必要があります。これはJSXのルールです!
続いて、CSSも当てて見た目を整えましょう!
今回はReactに集中したいので、CSSの解説は省略します。
App.css に以下のスタイルをコピペしてください👇
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
header {
height: 64px;
display: flex;
align-items: center;
background-color: #f8fafd;
display: flex;
justify-content: space-between;
}
header h1 {
font-size: 20px;
padding-left: 24px;
}
.list-creation {
width: 20vw;
height: 40px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: end;
padding: 0px 24px;
gap: 8px;
}
.list-creation h2 {
font-size: 16px;
margin-right: 8px;
}
.list-creation input {
width: 180px;
height: 32px;
padding: 4px;
border-radius: 4px;
border: 1px solid #ccc;
}
.list-creation button {
width: 72px;
height: 32px;
margin-left: 5px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
main {
background-color: #f8fafd;
display: flex;
flex-wrap: nowrap;
align-items: start;
height: calc(100vh - 64px);
padding: 0 12px 12px 12px;
overflow-x: auto;
}
.list {
background-color: white;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
min-width: 250px;
width: 250px;
margin: 0px 8px;
padding: 8px;
border-radius: 4px;
}
.list-header {
height: 40px;
padding: 8px 0px 0px 4px;
font-weight: bold;
font-size: 0.9em;
}
.cards-container {
display: flex;
flex-direction: column;
gap: 8px;
padding: 8px 8px;
}
.card {
background-color: white;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
padding: 6px;
border-radius: 4px;
display: flex;
justify-content: space-between;
align-items: center;
}
.card .delete {
z-index: 10;
color: red;
font-size: 0.8em;
margin-right: 8px;
}
.card :hover {
cursor: pointer;
}
.card:hover .delete:after {
content: "削除";
}
.list-footer {
height: 40px;
padding: 8px 0px 0px 4px;
font-weight: bold;
font-size: 0.9em;
}
.list-footer input {
width: 70%;
height: 32px;
padding: 4px;
border-radius: 4px;
border: 1px solid #ccc;
}
.list-footer button {
width: 25%;
height: 32px;
margin-left: 5px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
ブラウザで表示してみると、かなりToDoアプリっぽくなってるはず!
デザインはGoogleカレンダーのタスクっぽいイメージで作ってみました。
これでUI部分はひとまず完成です!
次は、実際にタスクを追加・削除できるように機能をつけていきましょう!
4. 機能を実装する
ここからはいよいよ「動く」ToDoアプリを作っていきます!
前回のカウンターアプリで出てきた、あの2つの主役も大活躍します。
- ボタンを押したときのイベント → onClick
- 状態を管理する変数 → useState
今回もこれらをガンガン使っていくので、復習しながらやっていきましょー!
4-1. タスクを状態変数で管理しよう
まずは、表示するタスクを状態変数として持たせて、あとで追加・削除できるようにします。
前回の復習通り、useStateを使ってこんな感じで書きます👇
+ import { useState } from "react";
import "./App.css";
const App = () => {
+ const [cards, setCards] = useState(["タスク1", "タスク2", "タスク3"]); // タスクを管理するための状態変数
// タスクを管理するための状態変数
return (
// 省略
);
};
export default App;
ここではcardsって名前にして、配列でタスクを持っています。
次はこの配列を画面に表示させていきます!
4-2. map関数で配列を展開しよう
配列の内容を画面に表示させるには、中身を1つ1つ取り出して並べる必要があります。このように、配列を展開する時に使うのが今回の新要素、map関数です!
map関数は、配列名.map()
の形で使います。
<div className="cards-container">
{cards.map((card,) => (
<div className="card">
<p>{card}</p>
<div className="delete"></div>
</div>
))}
</div>
「かっこ多くてムズっ😇」ってなるかもしれないので、ステップ分解して解説します👇
map関数を書く時に考えてること
①HTMLの中にJavaScript書きたいときは {}
で囲う
<div className="cards-container">
{cards.map()}
</div>
②map()
「アロー関数(()=>)」を使って処理を書く
※ 一行で書くなら {} じゃなくて () を使うのがポイント!
<div className="cards-container">
{cards.map(()=>())}
</div>
③配列の中身1個ずつ取り出す「引数」を書く(例:card)
配列名を複数形にして、その単数形を入れるのが普通です!
<div className="cards-container">
{cards.map((card)=>())}
</div>
④表示したい要素を書く(今回はdivとかpタグ)
<div className="cards-container">
{cards.map((card) => (
<div className="card">
<p className="card-text">{card}</p>
<div className="delete-button"></div>
</div>
))}
</div>
⑤完成!
ここまで来たら、状態変数の中身がちゃんと表示されてるのが確認できるはず!
スクショみたいに、3つのタスクが出ていればOK✨
4-3. タスクを追加できるようにしよう
次は、「タスクを追加する機能」を作っていきます!
input
に文字を入力して「追加」ボタンを押すと、新しいタスクがリストに追加される仕組みを作ります。
入力された内容をuseStateで管理しよう!
まずは、フォームの中に入力された文字を状態変数で管理する必要があります。
新しく inputValue という状態を追加しましょう。
import { useState } from "react";
import "./App.css";
const App = () => {
const [cards, setCards] = useState(["タスク1", "タスク2", "タスク3"]); // タスクを管理するための状態変数
+ const [inputValue, setInputValue] = useState(""); // 入力値を管理するための状態変数
return (
// 省略
);
};
export default App;
inputに状態変数を紐づけよう!
フォーム系の要素(input, textarea など)は、
「今入力されてる文字」を常に状態に反映させる必要があります。
<input
type="text"
className="add-todo"
placeholder="タスクを追加"
value={inputValue} // 入力値を表示
onChange={(e) => setInputValue(e.target.value)} // 入力値を更新
/>
これをApp.jsxにも組み込んであげましょう!
import { useState } from "react";
import "./App.css";
const App = () => {
// 省略
return (
<div>
{/* 省略 */}
<div className="list-footer">
<input
type="text"
className="add-todo"
placeholder="タスクを追加"
+ value={inputValue} // 入力値を表示
+ onChange={(e) => setInputValue(e.target.value)} // 入力値を更新
/>
<button className="add-button">追加</button>
</div>
</div>
</main>
</div>
);
};
export default App;
追加ボタンをクリックしたときの処理を作ろう!
次は「追加」ボタンを押したときの動きを作っていきます。
onClick イベントで、新しいタスクを今の cards に追加する関数を呼び出します。
import { useState } from "react";
import "./App.css";
const App = () => {
const [cards, setCards] = useState(["タスク1", "タスク2", "タスク3"]); // タスクを管理するための状態変数
const [inputValue, setInputValue] = useState(""); // 入力値を管理するための状態変数
+ // タスクを追加する関数
+ const handleAddTask = () => {
+ if (!inputValue) return; // 入力値が空の場合は何もしない
+
+ const newCards = [...cards, inputValue]; // 現在のタスクに新しいタスクを追加
+ setCards(newCards); // cardsを更新
+ setInputValue(""); // 入力フィールドを空にする
+ };
return (
<div>
<header>
<h1>ToDoアプリ</h1>
</header>
<main>
<div className="list">
{/* 省略 */}
<div className="list-footer">
<input
type="text"
className="add-todo"
placeholder="タスクを追加"
value={inputValue} // 入力値を表示
onChange={(e) => setInputValue(e.target.value)} // 入力値を更新
/>
- <button className="add-button">
+ <button className="add-button" onClick={handleAddTask}>
追加
</button>
</div>
</div>
</main>
</div>
);
};
export default App;
「...cards」ってなに? → スプレッド構文!
[...cards, inputValue]
これは cards の中身をまるっと展開して、最後に inputValue をくっつけた配列を新しく作ってるイメージ。
実際にこう書くのと同じ意味です👇
["タスク1", "タスク2", "タスク3", inputValue]
でも毎回「タスク1〜3」と書くのはしんどいので、スプレッド構文(...)を使うと便利&スッキリ!
これで、inputに文字を入れて「追加」ボタンを押すと、ちゃんとタスクが追加されるようになりました!
4-4. filter関数を使って、タスクを削除できるようにしよう
完了したタスク、いつまでも残ってたらスッキリしないですよね。ということで、今回は「タスク削除機能」をサクッと実装して
流れは追加ボタンとほぼ同じ。タスクを削除する関数を作って、削除ボタンにonClickで登録します。ただ、削除するときのcards
の更新で、新要素フィルター関数を使います!
import { useState } from "react";
import "./App.css";
const App = () => {
const [cards, setCards] = useState(["タスク1", "タスク2", "タスク3"]); // タスクを管理するための状態変数
const [inputValue, setInputValue] = useState(""); // 入力値を管理するための状態変数
// タスクを追加する関数
const handleAddTask = () => {
if (!inputValue) return; // 入力値が空の場合は何もしない
const newCards = [...cards, inputValue]; // 現在のタスクに新しいタスクを追加
setCards(newCards); // cardsを更新
setInputValue(""); // 入力フィールドを空にする
};
+ // タスクを削除する関数
+ const handleDeleteTask = (tempCard) => {
+ const newCards = cards.filter((card) => card !== tempCard); // 指定されたタスクを除外
+ setCards(newCards); // cardsを更新
+ };
return (
<div>
<header>
<h1>ToDoアプリ</h1>
</header>
<main>
<div className="list">
{/* 省略 */}
<div className="cards-container">
{cards.map((card) => (
<div className="card">
<p className="card-text">{card}</p>
<div
className="delete"
+ onClick={() => handleDeleteTask(card)}
></div>
</div>
))}
</div>
{/* 省略 */}
</div>
</main>
</div>
);
};
export default App;
ここで新たに登場したfilter関数は、「条件に一致するものだけを残す」関数です!
const 新しい配列 = 元の配列.filter((要素) => 条件);
今回はこう書きました。
const newCards = cards.filter((card) => card !== tempCard);
例えば、cards がこうだったとき:
["タスク1", "タスク2", "タスク3"]
タスク2
を消したい場合は:
cards.filter((card) => card !== "タスク2")
これで確認すると、きちんとタスクを削除できるはずです!
4. おわりに
これでToDoアプリは一旦完成です!今回は新要素が3つ登場しました!
- map関数
- スプレッド構文
- filter関数
今後、Reactで開発をする上でどれもよく使うものなので、しっかり復習しておいてください!
次回は、このToDoアプリをさらにグレードアップさせていきたいと思いますのでお楽しみに!