Reactの習得にあたって、こちらのUdemyの教材で学習をした内容についてまとめました。
結論めちゃめちゃわかりやすいのでおすすめです!
この講座はほんとにいいです!これまで何をやってもReactを全然理解できなかった中、辿り着いたこちらの講座に本当に救われました。
ReactでTODOアプリを作ります。
では行きましょう!
Reactで状態が変化するものはstateで定義するんでしたね!まずはそこからいきます!
stateの定義
未完了のstateを作り、初期値に「タスク1」と「タスク2」を入れます。
const [incompleteTodos, setIncompleteTodos] = useState([
"タスク1",
"タスク2"
]);
map関数を使い、引数をtodoとし、incompleteTodosの配列の中から値を順に取り出します。
<div className="incomplete-area">
<p>未完了のTODO</p>
<ul>
{incompleteTodos.map((todo) => {
return (
<div>
<li>{todo}</li>
<button>完了</button>
<button>削除</button>
</div>
);
})}
</ul>
</div>
key
Reactでループ処理を行う場合、ループ内で返却している一番親のタグにkeyを設定する必要がある。
これは、仮想DOMでは差分のみ反映していくため、何番目のものであるかがわかるようにするために必要になる。
ちなみにkeyの設定が無いと、以下のエラーとなる。
Warning: Each child in a list should have a unique "key" prop.
keyを設定した状態がこちら
{incompleteTodos.map((todo) => {
return (
<div key={todo} className="list-row">
<li>{todo}</li>
<button>完了</button>
<button>削除</button>
</div>
);
})}
フォームに入力したテキストを”未完了のTODO”に追加する
TODOの入力フォームを作成し、投稿したら未完了のTODOに追加されるようにします。
初期値と入力値を作る
- 入力値のvalueを変数todoTextとして、stateを作成する
- onChangeで、inputの変更の検知をする
- onChangeTodoText関数を設定し、event引数を受け取り、setTodoTextの関数に反映する
export const App = () => {
const [todoText, setTodoText] = useState("");
const onChangeTodoText = (event) => setTodoText(event.target.value);
return (
<>
<div className="input-area">
<input
placeholder="TODOを入力"
value={todoText}
onChange={onChangeTodoText}
/>
<button>追加</button>
</div>
省略
「追加」ボタンクリックで未完了リストに追加する
onClickAddが発火した時に、todoTextの値をincompleteTodosの配列に追加していく。
- 新たに追加される配列をnewTodosとする
- 更新関数setIncompleteTodosにnewTodosを設定
- 「追加」後にフォームを空にする処理と、空では追加できないようにする条件式の追加
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>
削除機能追加
incompleteTodosの配列から、対象のリストを削除する。
- 削除ボタンにonClickイベントを割り当て、クリックされたときの関数をonClickDeleteと定義する
- 何行目がクリックされたかがわかるよう引数にindexを設定する
incompleteTodos のmapで第二引数にindexを設定する。
<button onClick={onClickDelete(index)}>削除</button>
こうすると、この時点で関数が実行されてしまうため、常に表示されてしまう。
削除ボタンが実装された時に実行されてほしいので、アロー関数を書いて新しく関数を生成してあげるような処理とする。
<button onClick={() => onClickDelete(index)}>削除</button>
一旦alertで表示して確認してみる。
const onClickDelete = (index) => {
alert(index);
};
省略
return (
<>
省略
<div className="incomplete-area">
<p>未完了の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>
);
})}
</ul>
</div>
あとは、新しく配列を生成し、その中から指定したindexが削除されるように処理をする。
配列からの要素の削除にはspliceを使用します。第一引数に削除する要素の番号、第二引数に削除数を入れる。
最後に、更新関数setIncompleteTodosにnewTodosを入れる。
const onClickDelete = (index) => {
const newTodos = [...incompleteTodos];
newTodos.splice(index, 1);
setIncompleteTodos(newTodos);
};
完了機能
削除ボタンと同様にボタンクリックで”未完了のTODO”から削除するとともに、
“完了のTODO”に追加する。
const onClickComplete = (index) => {
const newIncompleteTodos = [...incompleteTodos];
newIncompleteTodos.splice(index, 1);
const newCompleteTodos = [...completeTodos, incompleteTodos[index]];
setIncompleteTodos(newIncompleteTodos);
setCompleteTodos(newCompleteTodos);
};
return (
<>
省略
<div className="incomplete-area">
<p>未完了の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>
以下の部分だけ解説
const newCompleteTodos = [...completeTodos, incompleteTodos[index]];
第一引数に、今ある”完了のTODO”の要素を、第二引数に今クリックした要素を設定する。
これで新しい”完了のTODO”の配列ができる。
戻す機能
「完了」から「未完了」に戻す処理を実装していきます。
- ボタンにonClickイベントを割り当て、関数onClickBackと引数indexを割り当てる
- 関数onClickBackを設定
新しく作られる”完了のTODO”newCompleteTodosに今のcompleteTodosを代入
newCompleteTodos の要素をspliceで削除していく - ”未完了のTODO”の newIncompleteTodos に追加していく
- それぞれ更新関数を呼びたし、再設定する
const onClickBack = (index) => {
const newCompleteTodos = [...completeTodos];
newCompleteTodos.splice(index, 1);
const newIncompleteTodos = [...incompleteTodos, completeTodos[index]];
setIncompleteTodos(newIncompleteTodos);
setCompleteTodos(newCompleteTodos);
};
省略
{completeTodos.map((todo, index) => {
return (
<div key={todo} className="list-row">
<li>{todo}</li>
<button onClick={() => onClickBack(index)}>戻す</button>
</div>
);
})}
ここまでのまとめ
コンポーネント化する前の状態でのここまでの全コードです。
次からコンポーネント化し、分解していきます。
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 onClickComplete = (index) => {
const newIncompleteTodos = [...incompleteTodos];
newIncompleteTodos.splice(index, 1);
const newCompleteTodos = [...completeTodos, incompleteTodos[index]];
setIncompleteTodos(newIncompleteTodos);
setCompleteTodos(newCompleteTodos);
};
const onClickDelete = (index) => {
const newTodos = [...incompleteTodos];
newTodos.splice(index, 1);
setIncompleteTodos(newTodos);
};
const onClickBack = (index) => {
const newCompleteTodos = [...completeTodos];
newCompleteTodos.splice(index, 1);
const newIncompleteTodos = [...incompleteTodos, completeTodos[index]];
setIncompleteTodos(newIncompleteTodos);
setCompleteTodos(newCompleteTodos);
};
return (
<>
<div className="input-area">
<input
placeholder="TODOを入力"
value={todoText}
onChange={onChangeTodoText}
/>
<button onClick={onClickAdd}>追加</button>
</div>
<div className="incomplete-area">
<p>未完了の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>完了の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>
</>
);
};
コンポーネント化
“TODOの追加部”と、”未完了のTODO”、”完了のTODO”の3つにコンポーネントを分ける。
componentsディレクトリを作成し、3つのコンポーネントファイルを作成する。
ファイル名は、コンポーネント名と同じのため、パスカルケースとする。
“TODOの追加部” のコンポーネント化
入力フォームのエリアを移行します。
ただ、そのまま移行すると設定した関数が無いため、エラーとなってしまうので、propsを使って、stateや関数等を渡す。
ただ移行しただけの状態
const InputTodo = () => {
return (
<div className="input-area">
<input
placeholder="TODOを入力"
value={todoText}
onChange={onChangeTodoText}
/>
<button onClick={onClickAdd}>追加</button>
</div>
);
};
ここにpropsを渡していく。
InputTodoファイル内で定義されていない関数は以下の3つ
todoText, onChangeTodoText, onClickAdd
App.jsファイルで以下のように記述し渡す。
return (
<>
<InputTodo todoText={todoText} onChange={onChangeTodoText} onClick={onClickAdd}/>
InputTodoファイル に、分割代入で渡していく。
export const InputTodo = (props) => {
const { todoText, onChange, onClick } = props;
return (
<div className="input-area">
<input placeholder="TODOを入力" value={todoText} onChange={onChange} />
<button onClick={onClick}>追加</button>
</div>
);
};
“未完了のTODO” のコンポーネント化
何をpropsとして渡す必要があるか?
stateのincompleteTodos
関数のonClickCompleteとonClickDelete
<IncompleteTodos
todos={incompleteTodos}
onClickComplete={onClickComplete}
onClickDelete={onClickDelete}
/>
export const IncompleteTodos = (props) => {
const { todos, onClickComplete, onClickDelete } = props;
return (
<div className="incomplete-area">
<p>未完了のTODO</p>
<ul>
{todos.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>
);
};
“完了のTODO” のコンポーネント化
同様に。
コンポーネント分割したのがこちら。
<CompleteTodos todos={completeTodos} onClickBack={onClickBack} />
export const CompleteTodos = (props) => {
const { todos, onClickBack } = props;
return (
<div className="complete-area">
<p>完了のTODO</p>
<ul>
{todos.map((todo, index) => {
return (
<div key={todo} className="list-row">
<li>{todo}</li>
<button onClick={() => onClickBack(index)}>戻す</button>
</div>
);
})}
</ul>
</div>
);
};
CSS-in-js
コンポーネントを分けた部分のCSSも、その分けたコンポーネントファイルに記述すること。
ただ、これは必ずしもする必要は無く開発チームによって使用するかどうかは異なる。
以下のInputTodoコンポーネントに以下のCSSを適用する場合
export const InputTodo = (props) => {
const { todoText, onChange, onClick } = props;
return (
<div className="input-area">
<input placeholder="TODOを入力" value={todoText} onChange={onChange} />
<button onClick={onClick}>追加</button>
</div>
);
};
.incomplete-area {
background-color: #c6ffe2;
}
CSSをコンポーネントに記述する時の注意点
- -の繫ぎをキャメルケースに変更
- 値を文字列に変更
- 最後のセミコロンをカンマに変更(オブジェクトという扱いだから)
divのところに指定しているclassNameは不要となり、styleでCSSを適用する。
移行後
const style = {
backgroundColor: "#c1ffff"
};
export const InputTodo = (props) => {
const { todoText, onChange, onClick } = props;
return (
<div style={style}>
<input placeholder="TODOを入力" value={todoText} onChange={onChange} />
<button onClick={onClick}>追加</button>
</div>
);
};
バリデーション・メッセージ
TODOリストは5個までしか保存できないというようにする。
- 5個登録された際に表示されるメッセージを用意
- incompleteTodosを基に条件式を書く
- 条件の5個に達したらinputエリアとボタンを非活性にする。非活性はdisabledを使う。
{incompleteTodos.length >= 5 && <p>登録は5個まで</p>}
4. propsとしてdisabledを受け取り、trueならdisabledにする(メソッドが機能する)ようにする。
export const InputTodo = (props) => {
const { todoText, onChange, onClick, disabled } = props;
return (
<div style={style}>
<input
disabled={disabled}
placeholder="TODOを入力"
value={todoText}
onChange={onChange}
/>
<button disabled={disabled} onClick={onClick}>
追加
</button>
</div>
);
};
5. App.jsのJSX内で以下のようにpropsを設定することで、5以上の時disableはtrueとなる
disabled={incompleteTodos.length >= 5}
以上、ReacrtでTODOをつくりました。
削除から戻す作成、削除、戻し機能があり、結構しっかりしたTODOを作成しました。
これでReactの基礎をマスターしました!
ご覧いただきありがとうございました。
コメント