前回、ReactでシンプルなToDoリストを作る方法の概要について解説しました。
今回はその続きで、個別のコンポーネントについて解説していきます。
同じようにReactを勉強している人の参考になれば幸いです!
【今回のToDoリストの動作はこちらから確認できます】
【ハンズオンとして使わせていただいたToDoリストの作り方を解説している動画はこちら】
Titleコンポーネントの解説
まずはTitleコンポーネントについて見ていきましょう。
と言っても、Titleコンポーネントはタイトルをヘッダーとして表示しているだけなので非常にシンプル。
import React from 'react'
export const Title = () => {
return (
<div>
<header>
<h1>ToDoList</h1>
</header>
</div>
)
}
returnとしてH1タグを含めたdivタグを返しています。
つまり、これらの要素が画面に表示されるんですね。
また、このTitleメソッドをエクスポートしているので、App.jsでインポートして呼び出すことができる訳です。
function App() {
return (
<div className="body">
<Title />
</div>
);
}
InputFormコンポーネントの解説
では次に、InputFormコンポーネントについて解説していきます。
App.jsからの呼び出し方
まず、App.jsからの呼び出し方について見ていきましょう。
import {useState} from 'react';
import { InputForm } from './components/InputForm';
function App() {
const [taskList, setTaskList] = useState([]);
return (
<div className="body">
<InputForm taskList={taskList} setTaskList={setTaskList} />
</div>
);
}
InputFormコンポーネントを呼び出す際、taskListとsetTaskListの2つを引数として渡しています。
左辺のtaskListは変数名、右辺の{taskList}は値だと思ってもらえばOKです。
また、このときtaskListとsetTaskListはどちらもuseStateだということも覚えておいてください。
InputForm.jsxの解説
App.jsで呼び出されるInputFormコンポーネントについて見ていきましょう。
import { useState } from "react";
export const InputForm = ({ taskList, setTaskList }) => {
const [inputText, setInputText] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
// 入力欄が空白なら実行しない
if (inputText.length !== 0) {
// タスクを追加する
setTaskList([
...taskList,
{
id: taskList.length,
text: inputText,
completed: false,
},
]);
// 入力した文字を消す
setInputText("");
}
};
const handleChange = (e) => {
setInputText(e.target.value);
};
return (
<div className="inputForm">
<form onSubmit={handleSubmit}>
<input type="text" onChange={handleChange} value={inputText} />
<button>
<i className="fas fa-plus"></i>
</button>
</form>
</div>
);
};
3行目でInputFormメソッドを定義しており、App.jsで設定されたuseStateのtaskListとsetTaskListを受け取っています。
こうすることで、useStateのtaskListがApp.js内だけでなく、このコンポーネント内でも使うことができるようになります。
また、InputFormメソッドで引数を受け取っている際、( taskList, setTaskList )ではなく({ taskList, setTaskList })と書かれています。
こちらは、オブジェクトの分割代入という考え方?らしいです。
オブジェクトの分割代入については、以下で詳しく解説されていたので、気になった方はご覧ください。
returnの中身を見ていきましょう。
formタグとinputタグ、buttonタグが使われていますね。
つまり、App.jsにはこれらformタグのセットが返されて、レンダリングすることになります。
そして、formタグでsubmitされるとhandleSubmitメソッドが、inputタグの値が変化する(onChange)とhandleChangeメソッドが呼び出されます。
これらのメソッドについて解説していきます。
handleChangeメソッド
まずはhand;eChangeメソッドです。
const handleChange = (e) => {
setInputText(e.target.value);
};
注目すべきは2点。
引数として受け取っている”e”と、更新関数であるsetInputTextです。
まず引数”e”ですが、こちらはイベントが実行されたときに渡される引数になります。
つまりeventの”e”ですね
今回の場合、onChangeの実行により呼び出されるので、変化した値が引数となります。
そして今回変化した値とは、inputフォーム内のvalueとなります。
<input type="text" onChange={handleChange} value={inputText} />
inputフォーム内のvalueは、useStateで宣言されている”inputText”ですね。
const [inputText, setInputText] = useState("");
ここでhandleChangeメソッド内を見てみましょう。
handleChangeメソッドでは、inputTextの更新関数であるsetInputTextが使われています。
const handleChange = (e) => {
setInputText(e.target.value);
};
このsetInputTextで渡しているのはe.target.valueです。
このとき、e.target.valueはinputタグ内のvalue、つまりinputTextを指しています。
例えば、inputタグ内のvalueではなくnameを参照したい場合はe.target.nameとなりますね。
そのため、このhandleChangeメソッドの動作としては、inputタグ内のvalueの値を更新関数を使ってinputTextへと上書きするものとなります。
handleSubmitメソッド
次にhandleSubmitメソッドです。
const handleSubmit = (e) => {
e.preventDefault();
// 入力欄が空白なら実行しない
if (inputText.length !== 0) {
// タスクを追加する
setTaskList([
...taskList,
{
id: taskList.length,
text: inputText,
completed: false,
},
]);
// 入力した文字を消す
setInputText("");
}
};
このhandleSubmitメソッドは、ボタンがクリックされることでonSubmitとして実行されます。
HTMLのformでSubmitすると、画面遷移が発生しますよね。
Reactでも同じくSubmitすると画面遷移が発生してしまうのですが、そうすると今回のアプリはうまく動きません。
そのため、画面遷移を発生させないために”e.preventDefault()”と実行しています。
ここでは、e.preventDefault()を実行することで、画面遷移が起こらなくなるとだけ覚えておいてもらえればOKです。
実際の処理で使用するのは、useStateとして宣言しているinputTextとtaskList、それらの更新関数であるsetInputTextとsetTaskListです。
まずはsetTaskListを見ていきましょう。
setTaskList([
...taskList,
{
id: taskList.length,
text: inputText,
completed: false,
},
]);
”…taskList”はスプレッド構文を使っています。
簡単に言うと、配列の中身を展開して使用する書き方です。
スプレッド構文についてはこちらのサイトの説明が非常にわかりやすかったので、もっと詳しく知りたい方はぜひどうぞ。
ここでは、taskListの中身を展開した後、その後ろに新たにオブジェクトをくっつけて、それをsetTaskListで更新しているんですね。
つまり、taskList配列にid・text・completedを持つ配列を追加しているのです。
このとき追加するtext要素は、先ほどhandleChangeメソッドで更新したinputTextです。
handleChangeメソッドでinputタグに入力した値をinputTextに入れて、handleSubmitメソッドでそのinputTextの値をtaskList配列に入れるイメージです。
そして、taskListを更新した後はsetInputText(“”)でinputタグのvalueを空にしています。
ToDoリストに登録した後は、入力欄をクリアするためですね。
また、入力欄に何も入力しないままでは登録できないようにif文で、1文字でも入力しないと実行できないようにしてあります。
TodoListコンポーネントの解説
最後にTodoListコンポーネントについて見ていきましょう。
import React from 'react'
export const TodoList = ({ taskList, setTaskList }) => {
const handleDelete = (id) => {
// タスクを削除する
setTaskList(taskList.filter((task) => task.id !== id));
}
const handleCompleted = (id) => {
// 取り消し線を追加する
setTaskList(taskList.map((task) => {
if (id === task.id) {
return {
...task,
completed: !task.completed
}
}
return task;
}))
}
return (
<div className='todoList'>
<div className="todos">
{taskList.map((task, index) => (
<div className={`todo ${task.completed ? "completed" : ""}`} key={index}>
<div className="todoText">
<span>{task.text}</span>
</div>
<div className="icons">
<button onClick={() => handleCompleted(task.id)}>
<i className="fas fa-check"></i>
</button>
<button onClick={() => handleDelete(task.id)}>
<i className="fas fa-trash"></i>
</button>
</div>
</div>
))}
</div>
</div>
)
}
こちらもInputForm.jsxと同様にtaskListとsetTaskListをApp.jsから引数として受け取っています。
ではまずレンダリングされる中身であるreturnを見ていきましょう。
処理の内容としては、taskListの中身をタスクリストとして表示させています。
またその際、完了ボタンや削除ボタンも一緒に表示させています。
taskListの中身を取り出すために、mapメソッドを使用しています。
{taskList.map((task, index) => (
<div className={`todo ${task.completed ? "completed" : ""}`} key={index}>
<div className="todoText">
<span>{task.text}</span>
</div>
<div className="icons">
<button onClick={() => handleCompleted(task.id)}>
<i className="fas fa-check"></i>
</button>
<button onClick={() => handleDelete(task.id)}>
<i className="fas fa-trash"></i>
</button>
</div>
</div>
))}
mapメソッドを簡単に説明すると、foreach文のように配列の中身を順番に取り出していくような動作ができるようになります。
今回は、取り出した値を”task”、インデックス番号を”index”としています。
取り出したtaskのtextをタスクとして表示させています。
<div className="todoText">
<span>{task.text}</span>
</div>
また、このタスクの親要素として、divタグのkeyにindexを入れてあります。
このとき、クラス名は”todo”の他に三項演算子で制御しています。
<div className={`todo ${task.completed ? "completed" : ""}`} key={index}>
この三項演算子は、わかりやすくif文で書くと以下のようになるイメージ。
if(task.completed) {
return "completed";
}
else {
return "";
}
task内のcompletedがTrueならクラス名に”completed”を追加、Falseなら追加しないという処理になります。
こちらはタスク完了時にグレーアウトさせるための処理です。
CSSでクラス名にcompletedが付いている要素はグレーアウトするようにしています。
ここからは、ボタンをクリックしたときの動作について解説していきます。
handleDeleteメソッド
まずは削除ボタンをクリックしたときに呼び出されるhandleDeleteメソッドです。
const handleDelete = (id) => {
setTaskList(taskList.filter((task) => task.id !== id));
}
taskListから条件に合致した要素をfilterメソッドを使って抽出し、その抽出した配列を更新関数setTaskListで更新している処理となります。
もう少し具体的に見ていきましょう。
taskListの配列要素を取り出して”task”へと格納します。
そしてtaskオブジェクトのidと、削除ボタンに設定されているidを比較して、同じでないならsetTaskListで更新、同じなら取り除きます。
つまり、taskList内から削除したいidの要素以外をsetTaskListで更新することで、削除したいidの要素を削除しているんです。
handleCompleteメソッド
タスク完了ボタンをクリックしたときに呼び出されるhandleCompleteメソッドです。
const handleCompleted = (id) => {
setTaskList(taskList.map((task) => {
if (id === task.id) {
return {
...task,
completed: !task.completed
}
}
return task;
}))
}
完了ボタンをクリックした要素のcompletedプロパティを反転させています。
具体的には、taskList配列にmapメソッドを使って、クリックしたidと同じidを持つ要素を探しています。
もしid同じであれば、そのtaskに対して反転処理を行います。
スプレッド構文を使って今のtaskの状態を出し、その中でcompletedだけ上書きしているイメージです。
completedプロパティを反転させることで、True・Falseが切り替わり、グレーアウトの制御ができるようになります。
シンプルなToDoリストの作り方まとめ
前回解説したReactでシンプルなToDoリストを作る方法の概要と合わせて、一通りソースコードの中身や動作について解説してきました。
ハンズオン動画を見ながらコードを書いてましたが、改めてこうやって記事を書くために見直していると、理解が全然できていない部分があったなーと実感できました。
アウトプットは本当に大事です・・・
今後もReactについて勉強を進めながら、そのアウトプットとしてこうやって解説記事を書けていけたらなと思ってます。
Reactの勉強を始めたばかりな人の参考になれば幸いです!