はじめに
①の記事:https://qiita.com/atsu123456789/items/c055959b288f53841324
②の記事:https://qiita.com/atsu123456789/items/9fc5b6bf02dafd9e0212
参考にしているもの↓
今回の記事ではReactのみを使ったTodoアプリの開発についてです。
バックエンドやDBなどは使っていないので、データの保存等はできませんが、Reactをどう動かしていくのかのイメージが大分つくのではないでしょうか。
対象者
- 基本的なプログラミングの考え方が分かる(メソッドなど)
- 最低限のJavascriptが分かる(自信ない方は①の記事へGo)
- 最低限のReactの書き方が分かる(自信ない方は②の記事へGo)
- ReactやNext.jsに興味がある
- 最低限のHTML、CSSが分かる
ひな形の作成
import React from 'react';
import ReactDom from 'react-dom'
import { App } from './App';
ReactDom.render(<App />, document.getElementById("root"));
import React from "react"
import './App.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>ああああ</li>
<button>完了</button>
<button>削除</button>
</div>
<div className="list-row">
<li>いいいい</li>
<button>完了</button>
<button>削除</button>
</div>
</ul>
</div>
<div className="complete-area">
<p className="title">完了のTODO</p>
<ul>
<div className="list-row">
<li>うううう</li>
<button>戻す</button>
</div>
</ul>
</div>
</>
);
};
CSSは適当に設定しています。
するとこんな感じ。
未完了のTODOのコンポーネント化
useStateを使って
const [incompleteTodos, setIncompleteTodos] = useState(['ああああ', 'いいいい']);
こうしてmap関数を用いることで
<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>
このように書き換えることが出来る。
ここでreturn内のdivタグにkeyを設定する。
これはレンダリングする際にkeyの変化のみを書き換えてねーっていうことを教えてあげるものらしい
完了のTODOのコンポーネント化
先ほどと同様にして完了のTODOも書き換える
const [completeTodos, setCompleteTodos] = useState(['うううう']);
<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>
追加ボタン押下時の処理
まずApp.jsxの内容を書き換える
<div className="input-area">
<input
placeholder='TODOを入力'
value={todoText}
onChange={onChangeTodoText}
/>
<button onClick={onClickAdd}>追加</button>
</div>
これでinputタグ内に文字を入力すると、onChangeTextという関数が、buttonクリック時にはonClickAddという関数が働く。
ではそれぞれの関数の処理を書いていく。
const onChangeTodoText = (e) => setTodoText(e.target.value);
const onClickAdd = () => {
if (todoText === "") return;
const newTodos = [...incompleteTodos, todoText];
setIncompleteTodos(newTodos);
setTodoText("");
};
onChangeTodoTextでは、inputタグの変化に応じてその値を取得し、toDoTextというStateで定義した値の中に入れる。
onClickAddでは上で指定したtodoTextが空でない場合に限り、incompleteTodos配列にtodoTextを追加し、新たなリストとして、Stateで定義したincompleteTodosに格納している。
こうすることで追加ボタン押下時に未完了のTODOに値が入るようになった。
削除の機能
次はこの削除ボタン押下時に未完了のTODOから要素が削除されるように実装していく。
まずは削除ボタン押下時に処理が走ってほしいので
<ul>
{ incompleteTodos.map((todo, index) => {
return (
<div key={todo} className="list-row">
<li>{todo}</li>
<button>完了</button>
<button onClick={() => onClickDelete(index)}>削除</button>
</div>
)
}) }
</ul>
onClickDeleteという関数を与える。
この時、何番目の要素を削除するかをわからせるためにmap関数でindexをとり、それをonClickDeleteに渡している。
※Reactでは関数に引数を渡したいとき、
<button onClick={() => onClickDelete(index)}>削除</button>
このように、アロー関数の形で書く必要がある。
では実際にonClickDeleteの処理を書いていく。
const onClickDelete = (index) => {
const newTodos = [...incompleteTodos];
newTodos.splice(index, 1);
setIncompleteTodos(newTodos);
};
やっていることは至極単純で、incompleteTodosからspliceを用いてindex番号目の要素を削除し、その配列をincompleteTodosに渡しているだけである。
これで削除機能の実装ができた。
完了機能の実装
次に完了ボタン押下時に、未完了のTODOから完了のTODOへ移行させる処理を書く。
削除の時と同様、完了ボタン押下時に処理を走らせたいので
<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>
完了ボタン押下時にonClickCompleteという関数を呼び出す。
ではonClickCompleteの処理を書いていく。
const onClickComplete = (index) => {
const newIncompleteTodos = [...incompleteTodos];
newIncompleteTodos.splice(index, 1);
const newCompleteTodos = [...completeTodos, incompleteTodos[index]];
setIncompleteTodos(newIncompleteTodos);
setCompleteTodos(newCompleteTodos);
};
ここも先ほどの削除と似たような処理を行っており、やっていることとしては
①incompleteTodosからnewIncompleteTodosを作成
②newIncompleteTodosのindex番目の要素を削除
③completeTodosとincompleteTodosのindex番目の要素を連結した配列をnewCompleteTodosとして定義
④incompleteTodos、completeTodosの中身を更新
これで完了ボタンの実装ができた。
戻るボタンの実装
戻すボタン押下時に完了のTODOから未完了のTODOに移行するように実装していく。
中身的には先ほどの完了の処理とほとんど同じである。
<ul>
{completeTodos.map((todo, index) => {
return (
<div key={todo} className="list-row">
<li>{ todo }</li>
<button onClick={() => onClickBack(index)}>戻す</button>
</div>
);
})}
</ul>
これでbutton押下時にonClickBackという関数が走るので、その関数を実装する
const onClickBack = (index) => {
const newCompleteTodos = [...completeTodos];
newCompleteTodos.splice(index, 1);
const newIncompleteTodos = [...incompleteTodos, completeTodos[index]];
setCompleteTodos(newCompleteTodos);
setIncompleteTodos(newIncompleteTodos);
};
こちらは先ほどの完了ボタンとほぼ逆のことをしているだけであり、
①completeTodosからnewCompleteTodosを作成
②newCompleteTodosのindex番目の要素を削除
③incompleteTodosとcompleteTodosのindex番目の要素を連結した配列をnewIncompleteTodosとして定義
④completeTodos、incompleteTodosの中身を更新
これで戻る機能の実装ができた。
↓App.jsxの最終形
import React from "react"
import { useState } from "react";
import './App.css';
export const App = () => {
const [todoText, setTodoText] = useState('');
const [incompleteTodos, setIncompleteTodos] = useState([]);
const [completeTodos, setCompleteTodos] = useState([]);
const onChangeTodoText = (e) => setTodoText(e.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);
const newCompleteTodos = [...completeTodos, incompleteTodos[index]];
setIncompleteTodos(newIncompleteTodos);
setCompleteTodos(newCompleteTodos);
};
const onClickBack = (index) => {
const newCompleteTodos = [...completeTodos];
newCompleteTodos.splice(index, 1);
const newIncompleteTodos = [...incompleteTodos, completeTodos[index]];
setCompleteTodos(newCompleteTodos);
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>
</>
);
};
コンポーネント化
ここまで進めると、とりあえず動くTodoアプリができた。
ただApp.jsxをみると、長いファイルとなっており、どこに何の処理をしているかが分かりにくい。
そこでコンポーネントの分割を行っていく。
import React from 'react';
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>
);
};
{/* ファイルのよびだしを忘れずに! */}
import { InputTodo } from './components/InputTodo'
{/* 40行目くらい */}
<InputTodo todoText={todoText} onChange={onChangeTodoText} onClick={onClickAdd} />
<div className="incomplete-area">
<p className="title">未完了のTODO</p>
./components/InputTodo.jsxは先ほどのApp.jsxのinputの部分を別のファイルに書き換えたものである。
App.jsxでInputTodoを呼び出し、引数としてtodoText、onChange、onClickを渡し、これを./components/InputTodo.jsxの中でpropsから分割代入することで値を取得している。
どうように他の要素もコンポーネント化すると先ほどの最終形が以下のようになる
import React from "react"
import { useState } from "react";
import './App.css';
import { InputTodo } from './components/InputTodo'
import { IncompleteTodos } from "./components/IncompleteTodos";
import { CompleteTodos } from "./components/CompleteTodos";
export const App = () => {
const [todoText, setTodoText] = useState('');
const [incompleteTodos, setIncompleteTodos] = useState([]);
const [completeTodos, setCompleteTodos] = useState([]);
const onChangeTodoText = (e) => setTodoText(e.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);
const newCompleteTodos = [...completeTodos, incompleteTodos[index]];
setIncompleteTodos(newIncompleteTodos);
setCompleteTodos(newCompleteTodos);
};
const onClickBack = (index) => {
const newCompleteTodos = [...completeTodos];
newCompleteTodos.splice(index, 1);
const newIncompleteTodos = [...incompleteTodos, completeTodos[index]];
setCompleteTodos(newCompleteTodos);
setIncompleteTodos(newIncompleteTodos);
};
return (
<>
<InputTodo
todoText={todoText}
onChange={onChangeTodoText}
onClick={onClickAdd}
/>
<IncompleteTodos
todos={incompleteTodos}
onClickComplete={onClickComplete}
onClickDelete={onClickDelete}
/>
<CompleteTodos
todos={completeTodos}
onClickBack={onClickBack}
/>
</>
);
};
だいぶスッキリ。
最終形はこんな感じで追加や完了などのボタンを押すと動きます。Reactってすごいね
最後に
ここまで読んでくれてありがとうございます。
とりあえずここまでで自分自身、なんとなくReactが見えてきたのでもう少し使い慣れたらNext.jsいじってみたいと思います。
④の記事は当分先になるかも(m´・ω・`)m ゴメン…
参考文献