#記事の内容
これまで学んだ知識を活かしつつ、JavaScriptの関数等を使用することでTODOアプリを無事作成することができました。
今回学んだことをアウトプットの意味も込めて実装内容を解説しておりますので、興味がある方は是非ともご覧くださいませ。
【追伸】
作成したTODOアプリをもとにコンポーネント化してみたので興味のある方は、下記URLから御覧くださいませ。
##完成形
最後に完成形のソースコードを載せています。
##前提
-
CodeSandbox
を使用
##実装手順
実装順 | ページ内リンク |
---|---|
1 | 実装する前の準備 |
2 | タスクの追加機能 |
3 | タスクの削除機能 |
4 | タスクの完了機能 |
5 | タスクの戻す機能 |
6 | 完成形のソースコード |
#実装する前の準備
ひな形の作成 + スタイルの付与を行い、静的なTODOアプリを作成しております。
import React from "react";
import ReactDOM from "react-dom";
import { App } from "./App";
ReactDOM.render(<App />, document.getElementById("root"));
import React from "react";
import "./styles.css";
export const App = () => {
return (
<>
<div className="input-area">
<input placeholder="TODOを入力" />
<button>追加</button>
</div>
<div className="incomplete-area">
<p className="title">未完了のTODO</p>
<ul>
<div className="list-row">
<li>タスク1</li>
<button>完了</button>
<button>削除</button>
</div>
<div className="list-row">
<li>タスク2</li>
<button>完了</button>
<button>削除</button>
</div>
</ul>
</div>
<div className="complete-area">
<p className="title">完了のTODO</p>
<ul>
<div className="list-row">
<li>タスク3</li>
<button>戻る</button>
</div>
</ul>
</div>
</>
);
};
-
jsx
の場合、クラスを付与する際はclassName="クラス名"
とする必要がある。
body {
font-family: sans-serif;
}
input {
border-radius: 16px;
border: none;
padding: 6px 16px;
outline: none;
}
button {
border-radius: 16px;
border: none;
padding: 4px 16px;
}
button:hover {
background-color: #ff7fff;
color: #fff;
cursor: pointer;
}
li {
margin-right: 8px;
}
.input-area {
background-color: #c1ffff;
width: 400px;
height: 30px;
border-radius: 8px;
padding: 8px;
margin: 8px;
}
.incomplete-area {
background-color: #c6ffe2;
width: 400px;
min-height: 200px;
padding: 8px;
margin: 8px;
border-radius: 8px;
}
.complete-area {
background-color: #ffffe0;
width: 400px;
min-height: 200px;
padding: 8px;
margin: 8px;
border-radius: 8px;
}
.title {
text-align: center;
margin-top: 0;
font-weight: bold;
color: #666;
}
.list-row {
display: flex;
align-items: center;
padding-bottom: 4px;
}
###<--- ここから機能の実装開始 --->
#ステートの管理
状態が変化するリストをuseState
で定義して動的に表示させます。
import React, { useState } from "react"; // useStateの読み込み
import "./styles.css";
export const App = () => {
// 未完了TODOを定義
const [incompleteTodos, setIncompleteTodos] = useState([
"タスク1",
"タスク2"
]);
// 完了TODOを定義
const [completeTodos, setCompleteTodos] = useState(["タスク3"])
return (
<>
<div className="input-area">
<input placeholder="TODOを入力" />
<button>追加</button>
</div>
<div className="incomplete-area">
<p className="title">未完了のTODO</p>
<ul>
{incompleteTodos.map((todo) => {
return (
<div key={todo} className="list-row">
<li>{todo}</li>
<button>完了</button>
<button>削除</button>
</div>
);
})}
</ul>
</div>
<div className="complete-area">
<p className="title">完了のTODO</p>
<ul>
{completeTodos.map((todo) => {
return (
<div key={todo} className="list-row">
<li>{todo}</li>
<button>戻る</button>
</div>
);
})}
</ul>
</div>
</>
);
};
-
未完了のTODOと完了のTODOを格納する変数を定義
- TODOは複数作成できるようにするため、配列
[]
で定義-
incompleteTodos
には未完了のTODOが格納(setIncompleteTodos
は後ほど解説します)- 画面に反映されるか確認するため、配列に仮値を格納しています。
-
-
map
関数でループ処理- JavaScriptを記述するため、ブラケット
{}
でコードを囲う。 -
map
の引数を設定 ->map((todo ← 引数) => {
- 引数には、先ほど定義した配列の仮値が格納されている(引数名は任意)
- TODOの表示
-
<li>{todo}</li>
とすることで配列に格納されている要素を順番に表示
-
-
key
の設定- ループ内で返却している親タグに
key={引数名}
を記述 -
React
は変更前と変更後(ここではTODOの追加や削除等の変更時)の差分のみ反映してレンダリングされるため、key
で要素を識別するための目印として記述する必要があります。
- ループ内で返却している親タグに
- JavaScriptを記述するため、ブラケット
- TODOは複数作成できるようにするため、配列
#タスクの追加機能
TODOを入力して追加ボタンをクリック後、未完了のTODOリストにタスクが追加されるまで
export const App = () => {
const [todoText, setTodoText] = useState("");
const [incompleteTodos, setIncompleteTodos] = useState([
"タスク1",
"タスク2"
]);
const [completeTodos, setCompleteTodos] = useState(["タスク3"]);
const onChangeTodoText = (event) => setTodoText(event.target.value);
const onClickAdd = () => {
if (todoText === "") return;
const newTodos = [...incompleteTodos, todoText];
setIncompleteTodos(newTodos);
setTodoText("");
};
return (
<>
<div className="input-area">
<input
placeholder="TODOを入力"
value={todoText}
onChange={onChangeTodoText}
/>
<button onClick={onClickAdd}>追加</button>
</div>
の実装内容を解説します。
const [todoText, setTodoText] = useState("");
- TODO入力欄の値を取得するため、入力部分を
state
として定義- 入力欄は最初は空なので
useState('')
として空文字を設定
- 入力欄は最初は空なので
<input placeholder="TODOを入力" value={todoText} onChange={onChangeTodoText} />
-
input
のvalue
属性に{todoText}
を設定することで、入力された値が表示されるようにしている。- 現時点では、空文字が設定した状態のため、入力しても値は反映されないので、、
-
onChange
イベントを使用して入力された値を取得し、入力欄に反映させる処理を関数に定義している。
const onChangeTodoText = (event) => setTodoText(event.target.value);
- 関数の引数(
event
)には、入力された情報が格納されており、event.target.value
とすることで入力された値を取得できる。- 取得した値を
setTodoText
の引数に渡すことで、todoText
のstate
に反映させている。
- 取得した値を
<button onClick={onClickAdd}>追加</button>
-
onClick
イベントを使用し、追加ボタン押下後の処理を定義した関数を設定
const onClickAdd = () => {
if (todoText === "") return;
const newTodos = [...incompleteTodos, todoText];
setIncompleteTodos(newTodos);
setTodoText("");
};
- if文で入力欄が空の場合、処理を実行しないよう制御している。
-
newTodos
変数には、今現在表示されているタスクと新たに追加したタスクを結合した新たな配列が格納されている。- **スプレッド構文
(...)
**を使用して今現在表示されているタスクを格納 -
todoText
には、新たに追加したタスクが格納されている。
- **スプレッド構文
-
setIncompleteTodos
の引数に渡すことで更新された内容が反映されて、未完了のTODOリストに表示される。 - 最後に
setTodoText("");
とすることで、追加後に入力欄をリセットしている。
#タスクの削除機能
削除ボタン押下後、該当タスクを削除
export const App = () => {
.
.
.
const onClickDelete = (index) => {
const newTodos = [...incompleteTodos];
newTodos.splice(index, 1);
setIncompleteTodos(newTodos);
};
return (
<>
.
.
.
<p className="title">未完了のTODO</p>
<ul>
{incompleteTodos.map((todo, index) => {
return (
<div key={todo} className="list-row">
<li>{todo}</li>
<button>完了</button>
<button onClick={() => onClickDelete(index)}>削除</button>
</div>
実装内容を解説します。
<button onClick={() => onClickDelete(index)}>削除</button>
- 削除ボタンに
onClick
イベントを設定-
map
関数の第2引数に設定したindex
には、タスクのインデックス番号が格納されており、その値をもとに削除したタスクの判定するため、onClickDelete
関数の引数に渡している。- 関数に引数を渡す場合、アロー関数
() =>
で新しく関数を生成させることで関数の実行を抑えている。- 記述しない場合、削除ボタンを押す前に関数が実行されてしまう。
- 関数に引数を渡す場合、アロー関数
-
const onClickDelete = (index) => {
const newTodos = [...incompleteTodos];
newTodos.splice(index, 1);
setIncompleteTodos(newTodos);
};
-
newTodos
(現在の未完了TODO)からsplice
メソッドを使用して削除したタスクを配列から取り除き、新たな配列を生成している。-
splice
の引数・・・(要素のインデックス番号, 取り除く要素数)
-
- 最後に
setIncompleteTodos
に更新後の配列を渡している。
#タスクの完了機能
完了ボタン押下後、未完了のTODOリストからタスクを削除し、完了のTODOリストに表示させる。
export const App = () => {
.
.
.
const onClickComplete = (index) => {
const newIncompleteTodos = [...incompleteTodos];
newIncompleteTodos.splice(index, 1);
setIncompleteTodos(newIncompleteTodos);
const newCompleteTodos = [...completeTodos, incompleteTodos[index]];
setCompleteTodos(newCompleteTodos);
};
return (
<>
.
.
.
<p className="title">未完了のTODO</p>
<ul>
{incompleteTodos.map((todo, index) => {
return (
<div key={todo} className="list-row">
<li>{todo}</li>
<button onClick={() => onClickComplete(index)}>完了</button>
<button onClick={() => onClickDelete(index)}>削除</button>
</div>
実装内容を解説します。
<button onClick={() => onClickComplete(index)}>完了</button>
-
onClick
イベントは、先ほどの削除ボタンと同様
const onClickComplete = (index) => {
const newIncompleteTodos = [...incompleteTodos];
newIncompleteTodos.splice(index, 1);
setIncompleteTodos(newIncompleteTodos);
const newCompleteTodos = [...completeTodos, incompleteTodos[index]];
setCompleteTodos(newCompleteTodos);
};
- 未完了のTODOリストからタスクを削除する実装は、変数名以外は先ほどと同様
-
[...completeTodos, incompleteTodos[index]];
-
newCompleteTodos
変数には、完了TODOリストに表示されているタスクと完了したタスクを結合した新たな配列が格納されている。- 配列「
incompleteTodos
」のあとに、ブラケット記法[]
で完了したタスクのインデックス番号「index
」を設定することで完了したタスクを取得できる。
- 配列「
-
- 最後に
setCompleteTodos
に更新後の配列を渡している。
#タスクの戻す機能
戻すボタン押下後、完了のTODOリストからタスクを削除し、未完了のTODOリストに表示させる。
export const App = () => {
.
.
.
const onClickBack = (index) => {
const newCompleteTodos = [...completeTodos];
newCompleteTodos.splice(index, 1);
setCompleteTodos(newCompleteTodos);
const newIncompleteTodos = [...incompleteTodos, completeTodos[index]];
setIncompleteTodos(newIncompleteTodos);
};
return (
<>
.
.
.
<div className="complete-area">
<p className="title">完了のTODO</p>
<ul>
{completeTodos.map((todo, index) => {
return (
<div key={todo} className="list-row">
<li>{todo}</li>
<button onClick={() => onClickBack(index)}>戻る</button>
</div>
実装内容を解説します。
{completeTodos.map((todo, index) => {
return (
<div key={todo} className="list-row">
<li>{todo}</li>
<button onClick={() => onClickBack(index)}>戻る</button>
-
onClick
イベントを設定し、戻したタスクを取得するためにmap
関数の引数にindex
を追加しています。 - 完了のTODOリストからタスクを削除及び、未完了のTODOリストにタスクを表示する実装は、変数名以外は先ほどと同様です。
#完成形のソースコード
最後に配列の初期値を削除し、空の配列[]
を設定したらTODOアプリの完成です。
import React, { useState } from "react";
import "./styles.css";
export const App = () => {
const [todoText, setTodoText] = useState("");
const [incompleteTodos, setIncompleteTodos] = useState([]);
const [completeTodos, setCompleteTodos] = useState([]);
const onChangeTodoText = (event) => setTodoText(event.target.value);
const onClickAdd = () => {
if (todoText === "") return;
const newTodos = [...incompleteTodos, todoText];
setIncompleteTodos(newTodos);
setTodoText("");
};
const onClickDelete = (index) => {
const newTodos = [...incompleteTodos];
newTodos.splice(index, 1);
setIncompleteTodos(newTodos);
};
const onClickComplete = (index) => {
const newIncompleteTodos = [...incompleteTodos];
newIncompleteTodos.splice(index, 1);
setIncompleteTodos(newIncompleteTodos);
const newCompleteTodos = [...completeTodos, incompleteTodos[index]];
setCompleteTodos(newCompleteTodos);
};
const onClickBack = (index) => {
const newCompleteTodos = [...completeTodos];
newCompleteTodos.splice(index, 1);
setCompleteTodos(newCompleteTodos);
const newIncompleteTodos = [...incompleteTodos, completeTodos[index]];
setIncompleteTodos(newIncompleteTodos);
};
return (
<>
<div className="input-area">
<input
placeholder="TODOを入力"
value={todoText}
onChange={onChangeTodoText}
/>
<button onClick={onClickAdd}>追加</button>
</div>
<div className="incomplete-area">
<p className="title">未完了のTODO</p>
<ul>
{incompleteTodos.map((todo, index) => {
return (
<div key={todo} className="list-row">
<li>{todo}</li>
<button onClick={() => onClickComplete(index)}>完了</button>
<button onClick={() => onClickDelete(index)}>削除</button>
</div>
);
})}
</ul>
</div>
<div className="complete-area">
<p className="title">完了のTODO</p>
<ul>
{completeTodos.map((todo, index) => {
return (
<div key={todo} className="list-row">
<li>{todo}</li>
<button onClick={() => onClickBack(index)}>戻る</button>
</div>
);
})}
</ul>
</div>
</>
);
};
##参考教材