0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Reactを使ったTODOアプリ制作

Posted at

この記事ではReactを使ったTODOアプリ制作の過程をアウトプット目的で記録していく。
なお、このアプリはUdemyの動画教材をもとに作成したものであり、オリジナルではありません。

利用したUdemyの動画はこちら。

制作するTODOアプリ概要

以下の画像のようなWebアプリを制作していく。

画面構成

・TODOを追加するエリア
・未完了のTODOエリア
・完了のTODOエリア

機能

・「追加」すると未完了のTODOに格納され、「完了」すると完了のTODOに移動する。
・「削除」すると未完了のTODOから削除され、「戻す」と未完了のTODOに移動する。
・TODO上限は5つまでとし、上限に達した場合はタスクを消化するよう警告が表示される。

スクリーンショット 2024-09-17 11.45.18.png

ディレクトリ構成

初期段階のsrcフォルダには3つのファイルが格納されている。
・Todo.jsx
・index.jsx
・style.css

コーディング開始

流れ

1. Todo.jsxにマークアップを記述
2. style.cssにスタイルを記述
3. 未完了のTODOをstateを意識したマークアップに改善
4. 完了のTODOをstateを意識したマークアップに改善
5.タスクの追加機能の実装
6.タスクの削除機能の実装
7.タスクの完了機能の実装
8.タスクの戻す機能の実装
9.stateの初期値を削除する
10.コンポーネント化する
11.各コンポーネントに該当するcssのスタイルもコンポーネントで分ける
12.Todoの上限設定

1. Todo.jsxにマークアップを記述

マークアップはJSX記法に基づいて行なっていく。
最終的にはコンポーネントを使ってTODOの内容を表現するが、現段階では未完了のTODOと完了のTODOにそれぞれ仮のTODOを表示させておく。

Todo.jsx
import "./style.css";

export const Todo = () => {
    return (
        <>
          <div>
              <input placeholder="TODOを入力" />
              <button>追加</button>
          </div>
          <div>
              <p>未完了のTODO</p>
              <ul>
                  <li>
                      <div>
                          <p>これからTODO</p>
                          <button>完了</button>
                          <button>削除</button>
                      </div>
                  </li>
                  <li>
                      <div>
                          <p>これからTODO</p>
                          <button>完了</button>
                          <button>削除</button>
                      </div>
                  </li>
              </ul>
         </div>
          <div>
              <p>完了のTODO</p>
              <ul>
                  <li>
                      <div>
                          <p>終わったTODO</p>
                          <button>戻す</button>
                      </div>   
                  </li>
                  <li>
                      <div>
                          <p>終わったTODO</p>
                          <button>戻す</button>
                      </div>
                  </li>
              </ul>
          </div>
        </>
    );
};

2. style.cssにスタイルを記述

最終的にスタイルはコンポーネントごとに記述を分けることになるが、現段階では全てstyle.cssに記述していく。
全ての要素に共通するスタイルは、このままstyle.cssに残ることになる。

style.css
body {
  font-family: sans-serif;
  color: #555;
  font-weight: bold;
}

input {
  border-radius: 8px;
  border: none;
  padding: 8px 16px;
  margin-right: 5px;
}

button {
  border-radius: 8px;
  border: none;
  padding: 4px 16px;
  margin: 0px 2px;
}

button:hover {
  background-color: #79a8a9;
  color: #fff;
  cursor: pointer;
}

.input-area {
  background-color: #c6e5d9;
  width: 400px;
  height: 30px;
  padding: 8px;
  margin: 8px;
  border-radius: 8px;
} 

.incomplete-area {
  border: solid 2px #79a8a9;
  border-radius: 8px;
  width: 400px;
  min-height: 200px;
  padding: 8px;
  margin: 8px;
}

.complete-area {
  border: solid 2px #79a8a9;
  background-color: #c6e5d9;
  border-radius: 8px;
  width: 400px;
  min-height: 200px;
  padding: 8px;
  margin: 8px;
}

.title {
  text-align: center;
  margin-top: 0;
  font-weight: bold;
}

.list-row {
  display: flex;
  align-items: center;
}

.todo-item {
  margin: 6px;
}

スタイルの適用に伴って、Todo.jsxにclassを追加する。

Todo.jsx
import "./style.css";

export const Todo = () => {
    return (
        <>
          <div className="input-area">
              <input placeholder="TODOを入力" />
              <button>追加</button>
          </div>
          <div className="incomplete-area">
              <p className="title">未完了のTODO</p>
              <ul>
                  <li>
                      <div className="list-row">
                          <p className="todo-item">これからTODO</p>
                          <button>完了</button>
                          <button>削除</button>
                      </div>
                  </li>
                  <li>
                      <div className="list-row">
                          <p className="todo-item">これからTODO</p>
                          <button>完了</button>
                          <button>削除</button>
                      </div>
                  </li>
              </ul>
         </div>
          <div className="complete-area">
              <p className="title">完了のTODO</p>
              <ul>
                  <li>
                      <div className="list-row">
                          <p className="todo-item">終わったTODO</p>
                          <button>戻す</button>
                      </div>   
                  </li>
                  <li>
                      <div className="list-row">
                          <p className="todo-item">終わったTODO</p>
                          <button>戻す</button>
                      </div>
                  </li>
              </ul>
          </div>
        </>
    );
};

3. 未完了のTODOをstateを意識したマークアップに改善

未完了のTODOと完了のTODOのエリアは常に状態が変化していく要素となる。
状態なのでstateで管理することになるため、それに合わせたマークアップにしていく。

未完了のTODO一覧をstateで定義する

未完了のTODOは複数の要素となるので配列として定義する。
この時stateの定義にはuseStateを用いることを忘れずに。

useStateについてはこちら

今回useStateにはinCompleteTodosとセット関数setIncompleteTodosを配列に用意。
useStateの初期値には仮の配列としてこれからTODO1これからTODO2を設定。
useStateのAuto importも忘れずに。

Todo.jsx
import {useState} from "react";
import "./style.css";

export const Todo = () => {
    const [inCompleteTodos, setInCompleteTodos] = useState(["これからTODO1", "これからTODO2"]);
    return (
        <>
            //省略
        </>
    );
};

stateをもとにmapメソッドを使ってliタグ部分を表示する

具体的には、配列のstateをもとに画面の要素を一覧で出していく部分をループしながらレンダリングしていく。
Reactでの一覧表示は、配列をmapでループしながら一つずつ新しい要素を返却することで行う。

mapはメソッドなので関数(アロー関数)形式で記述し、引数にループする各要素が入る。
返却するのはliタグの中身なので、liタグ部分を丸々関数の中にコピペする。

pタグの中身をmapメソッドの引数に修正し、最終的なコードは以下のようになる。

Todo.jsx
import {useState} from "react";
import "./style.css";

export const Todo = () => {
    const [inCompleteTodos, setInCompleteTodos] = useState(["これからTODO1", "これからTODO2"]);
    return (
        <>
         //省略
          <div className="incomplete-area">
              <p className="title">未完了のTODO</p>
              <ul>
                {inCompleteTodos.map((todo) => {
                    return (
                        <li>
                            <div className="list-row">
                                <p className="todo-item">{todo}</p>
                                <button>完了</button>
                                <button>削除</button>
                            </div>
                        </li>                    
                    );
                })};
              </ul>
         </div>
          //省略
        </>
    );
};

mapメソッドについてはこちら

keyを設定する

mapfilterにおいて、配列の値をもとに一覧表示をする時には、keyの設定が必要となる。
key設定は配列をループしてreturnしている一番上の要素に行い、今回はreturn直下のliタグが該当する。
例えば<li key={todo}>のように設定する。

なぜkeyが必要なのか

Reactの仮想DOMは変更前と変更後の差分だけ抽出して、その差分のみ実際のDOMに反映していく。
そのため今回のようにループでレンダリングする場合、何個目の要素なのかを正確に比較するために目印をつけてあげる必要がある。
それでループしている各要素の一意になる項目をkeyに設定することとなる。

アロー関数の特性を活かして{}returnを省略する

今回returnの中身がひとまとまりとなっているので、アロー関数の省略記法を用いて記述を簡潔にしていく。

Todo.jsx
//省略

export const Todo = () => {
    const [inCompleteTodos, setInCompleteTodos] = useState(["これからTODO1", "これからTODO2"]);
    return (
        <>
         //省略
          <div className="incomplete-area">
              <p className="title">未完了のTODO</p>
              <ul>
                {inCompleteTodos.map((todo) => (
                        <li key={todo}>  //keyの設定
                            <div className="list-row">
                                <p className="todo-item">{todo}</p>
                                <button>完了</button>
                                <button>削除</button>
                            </div>
                        </li>                    
                    ))}
              </ul>
         </div>
          //省略
        </>
    );
};

4. 完了のTODOをstateを意識したマークアップに改善

未完了のTODOと同様の流れで完了のTODOをstateで管理する記述に修正していく。

Todo.jsx
//省略

export const Todo = () => {
    const [inCompleteTodos, setInCompleteTodos] = useState(["これからTODO1", "これからTODO2"]);
    const [completeTodos, setCompleteTodos] = useState(["終わったTODO1", "終わったTODO2"]);
    return (
        <>
         //省略
          <div className="incomplete-area">
              <p className="title">未完了のTODO</p>
              <ul>
                {inCompleteTodos.map((todo) => (
                        <li key={todo}>  //keyの設定
                            <div className="list-row">
                                <p className="todo-item">{todo}</p>
                                <button>完了</button>
                                <button>削除</button>
                            </div>
                        </li>                    
                    ))}
              </ul>
         </div>
         <div className="complete-area">
              <p className="title">完了のTODO</p>
              <ul>
                  {completeTodos.map((todo) => (
                            <li key={todo}>
                              <div className="list-row">
                                  <p className="todo-item">{todo}</p>
                                  <button>戻す</button>
                              </div>   
                            </li>
                      ))}
              </ul>
          </div>
        </>
    );
};

5.タスクの追加機能の実装

テキストボックスに入力した内容を、追加ボタンを押したら未完了のTODOに追加されるように機能を実装していく。
イメージは以下の通り

  • inputに入力した内容を取得する
  • それをinCompleteTodosの配列の中に追加する
  • するとmapのループによって自動的に表示される

この時「入力された状態はなんなのか」という状態を管理するためstateを用いる。
新しく以下のuseStateの記述を追加する。
state名はtodoText、更新関数はsetTodoTextとし、初期値には空文字を定義しておく。

Todo.jsx
export const Todo = () => {
    const [todoText, setTodoText] = useState(""); //追加
    const [inCompleteTodos, setInCompleteTodos] = useState(["これからTODO1", "これからTODO2"]);
    const [completeTodos, setCompleteTodos] = useState(["終わったTODO1", "終わったTODO2"]);
    return ( //省略

この入力された内容、すなわちtodoTextという値はinputにおけるvaleに当たるので、value属性にtodoTextを設定しておく。

Todo.jsx
export const Todo = () => {
    const [todoText, setTodoText] = useState(""); //追加
    const [inCompleteTodos, setInCompleteTodos] = useState(["これからTODO1", "これからTODO2"]);
    const [completeTodos, setCompleteTodos] = useState(["終わったTODO1", "終わったTODO2"]);
    return ( 
            <>
              <div className="input-area">
                  <input placeholder="TODOを入力" value={todoText} /> //属性を追加
                  <button>追加</button>
              </div>
             //省略

例えばこの状態でuseStateの初期値にtestと入力すると、画面のinput部分には「test」 と表示されるようになる。
しかし、表示画面からテキストボックスに直接入力しようとしても入力できない状態になっている。
これは、初期値が空文字に設定していて、その値を常にvalueに設定しているので常に空文字状態になってしまっていることが原因。

解決方法として、テキストボックスにユーザーが変更を加えたことを検知して、それをもとに常にtodoTextstateを更新するという処理を実装する。

inputの入力内容の変更を検知させる

inputの入力内容を自動で検知するにはonChangeイベントを用いる。
onChangeはテキストボックスに変更があったときに実行されるイベント。

  • inputタグ内にイベントを追加
    <input placeholder="TODOを入力" value={todoText} onChange={} />
  • onChangeが実行された時の関数を定義
    onChangeTodoTextという名前で定義するので、その関数をonChangeイベントで呼び出す。
    <input placeholder="TODOを入力" value={todoText} onChange={onChangeTodoText} />
  • 関数の中身を入力された内容を検知するように記述
    このようなイベントは自動でeventという引数が渡ってくる。
    そして、setTodoTexteventtargetvalueというところに入力された文字が入ってくるように関数を書く。
Todo.jsx
export const Todo = () => {
    //省略
    const onChageTodoText = (event) => setTodoText(event.target.value);
    
    return ( 
            <>
              <div className="input-area">
                  <input placeholder="TODOを入力" value={todoText} onChange={onChangeTodoText} />
                  <button>追加</button>
              </div>
             //省略

結果としてinputに値が入力されるとonChangeイベントが発火し、その内容をもとにtodoTextstateを更新して、そのtodoTextの値がvalueに常に設定されるという流れを作ることができた。
inputに入力された値とtodoTextstateの値が常にイコール状態を保てる状態になったので、入力した値が`value'となって画面に表示される。

追加ボタンを押した時の処理を作る

  • buttonタグにクリックイベントを割り当てる。
  • そしてイベントが発火した時の関数としてonClickAddという関数を定義する。
Todo.jsx
export const Todo = () => {
    const [todoText, setTodoText] = useState(""); //追加
    const [inCompleteTodos, setInCompleteTodos] = useState(["これからTODO1", "これからTODO2"]);
    const [completeTodos, setCompleteTodos] = useState(["終わったTODO1", "終わったTODO2"]);
    
    const onChageTodoText = (event) => setTodoText(event.target.value);

    const onClickAdd = () => {
        
    };
    
    return ( 
            <>
              <div className="input-area">
                  <input placeholder="TODOを入力" value={todoText} onChange={onChangeTodoText} />
                  <button onClick={onClickAdd}>追加</button>
              </div>
             //省略
  • テキストボックスに入力された内容をinCompleteTodoの配列に追加する
    配列に追加する、つまり配列の結合なのでスプレッド構文を使用する。

まず追加した新しい配列を格納する関数をnewTodosとして用意する。
そして新しい配列を生成したいので、スプレッド構文を使って現在のinCompleteTodosを配列で設定。
(inCompleteTodosの配列のコピーをnewTodosとして生成)

Todo.jsx
export const Todo = () => {
    const [todoText, setTodoText] = useState(""); //追加
    const [inCompleteTodos, setInCompleteTodos] = useState(["これからTODO1", "これからTODO2"]);
    const [completeTodos, setCompleteTodos] = useState(["終わったTODO1", "終わったTODO2"]);
    
    const onChageTodoText = (event) => setTodoText(event.target.value);

    const onClickAdd = () => {
        const newTodos = [...inCompleteTodos];
    };
    
    return ( 
            <>
              <div className="input-area">
                  <input placeholder="TODOを入力" value={todoText} onChange={onChangeTodoText} />
                  <button onClick={onClickAdd}>追加</button>
              </div>
             //省略

スプレッド構文についてはこちら

newTodosにコピーした配列に追加したい要素はtodoTextの値なので、newTodosの配列に追記する。
const newTodos = [...inCompleteTodos, todoText];

そして、新しい要素が追加された配列newTodosを更新される値、つまりsetInCompleteTodosに渡せば現在の状態に新たな配列が追加された状態になる。

Todo.jsx
export const Todo = () => {
    const [todoText, setTodoText] = useState(""); //追加
    const [inCompleteTodos, setInCompleteTodos] = useState(["これからTODO1", "これからTODO2"]);
    const [completeTodos, setCompleteTodos] = useState(["終わったTODO1", "終わったTODO2"]);
    
    const onChageTodoText = (event) => setTodoText(event.target.value);

    const onClickAdd = () => {
        const newTodos = [...inCompleteTodos, todoText];
        setInCompleteTodoText(newTodos);
    };
    
    return ( 
            <>
              <div className="input-area">
                  <input placeholder="TODOを入力" value={todoText} onChange={onChangeTodoText} />
                  <button onClick={onClickAdd}>追加</button>
              </div>
             //省略

この段階で、テキストボックスに入力した内容を追加ボタンをクリックすることで未完了のTODOに追加することができるようになった。

追加した時にテキストボックスの入力を空にする

これは簡単で、setInCompleteTodoTextnewTodosを渡した後に、setTodoTextに空文字を渡すだけで処理が完了する。

Todo.jsx
export const Todo = () => {
    //省略
    const onClickAdd = () => {
        const newTodos = [...inCompleteTodos, todoText];
        setInCompleteTodoText(newTodos);
        setTodoText(""); //空文字を設定
    };
          //省略

また、現在の状態では何も入力してない時に追加ボタンをクリックすると、空のTODOが追加されてしまうので、その問題を条件式で解消する。

todoTextが空の時はreturnさせる

テキストボックスが状態では追加できないように条件式を記述する。
これで空文字の時はif文の行でreturnされるためそこで処理が止まり、その下の配列の追加処理は行われなくなるため、空文字のTODOは追加できなくなった。

Todo.jsx
export const Todo = () => {
    //省略
    const onClickAdd = () => {
        if (todoText === "") return;
        const newTodos = [...inCompleteTodos, todoText];
        setInCompleteTodoText(newTodos);
        setTodoText(""); //空文字を設定
    };
          //省略

6.タスクの削除機能の実装

削除ボタンにクリックイベントを付与し、削除ボタンが押されたときに、押された対象の行のcompleteTodosの要素を削除するという流れの機能になる。

イベントを用意し関数を定義する

イベントで呼び出す関数名はonCkickDeleteとする。

Todo.jsx
export const Todo = () => {
    //省略
    const onClickDelete = () => {}
    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) => (
                        <li key={todo}>  
                            <div className="list-row">
                                <p className="todo-item">{todo}</p>
                                <button>完了</button>
                                <button onClick={onClickDelete}>削除</button>
                            </div>
                        </li>                    
                    ))}
              </ul>
          </div>

削除されたのが配列の何番目かを取得する

現在mapの部分には何番目かを判定する要素がないので、それを追加していく。
mapの第二引数にはindexが入ってくるのでこれを活用する。
indexは0番目のループ、1番目のループというようにインデックス番号を表す引数。

mapの第二引数にindexを渡し、onClickDelete関数の引数にもindexを渡すことで、onClickDeleteindexを引数として受け取る関数になる。
そして試しにalertindexの値が表示されるようにし、イベントの引数にもindexを持たせることでアラートを実装する。

Todo.jsx
export const Todo = () => {
    //省略
    const onClickDelete = (index) => {
        alet(index);
    }
    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) => (
                        <li key={todo}>  
                            <div className="list-row">
                                <p className="todo-item">{todo}</p>
                                <button>完了</button>
                                <button onClick={onClickDelete(index)}>削除</button>
                            </div>
                        </li>                    
                    ))}
              </ul>
          </div>

すると常にアラートが表示されるという、アラートが暴走するバグが発生してしまう。
原因は、イベント部分を{}で記述しているためJSとして解釈されてしまい、ループでその部分を通るたびに関数が実行されてしまっているから。

それでJSX記法をしている部分から引数を設定した関数を実行したときは、{}の中に関数を生成してあげる。
<button onClick={() => onClickDelete(index)}>削除</button>
そうすることで{}の中身は関数としてではなく、関数を実行した時の処理として認識されるようになる。
すなわち、イベントが発火した時にonClickDeleteが即時実行されることがなくなったので、アラートの暴走が止まる。

この状態で未完了のTODOにあるリストの削除ボタンをクリックすると、アラートでindexの値が正常に表示されるようになる。

取得したindexをもとにinCompleteTodosからn番目の値を削除する処理を実装

削除した後の新しい配列を取得したいので、追加の時と同様にnewTodosという関数を用意。
そしてスプレッド構文を用いて'newTodos'に今の未完了リストの一覧、すなわちinCompleteTodosをコピーした配列を用意する。

Todo.jsx
export const Todo = () => {
    //省略
    const onClickDelete = (index) => {
        const newTodos = [...inCompleteTodos];
    }
    return ( 
        //省略
  • コピーした配列に対してindex番目の要素を削除する機能を実装
    JSのspliceメソッドを使う。
    spliceは第一引数にindex、第二引数に1を指定することで、何番目のindexの要素から1個削除するという処理になる。
Todo.jsx
export const Todo = () => {
    //省略
    const onClickDelete = (index) => {
        const newTodos = [...inCompleteTodos];
        newTodos.splice(index, 1)
    }
    return ( 
        //省略

上記の記述だと、onClickDeleteの引数indexが0番目だとしたら、0番目の要素から1個削除するという機能になっている。
つまり0番目の要素だけ削除することがでできる。

最後にsetInCompleteTodosnewTodosを渡せばいよい。

Todo.jsx
export const Todo = () => {
    //省略
    const onClickDelete = (index) => {
        const newTodos = [...inCompleteTodos];
        newTodos.splice(index, 1)
        setInCompleteTodos(newTodos);
    }
    return ( 
        //省略

これで、新しいTODOの追加と削除ボタンの機能の実装が完了した。

7.タスクの完了機能の実装

未完了のTODOの完了ボタンをクリックすると、完了のTODOに移動する処理を実装していく。
完了ボタンも削除ボタンと同様に「何番目の要素を」という考え方を使うので、削除ボタンのクリックイベントと同じ方で実装することができる。

関数名はonClickCompleteとし引数にindexを持たせる。
そして削除ボタンと同様に関数を定義し、引数にindexを持たせておく。

  • 完了を押したら未完了TODOから削除する
  • 完了を押したら完了TODOの一番下に追加する

という処理が必要になるので、順に行っていく。

未完了TODOから削除する

削除の部分はonClickDelete関数の上2行をコピペする。
ただし変数の名前だけ、今回は完了したTODOも対象になってくるのでnewTodosではなくnewInCompleteTodosとしておく。

Todo.jsx
export const Todo = () => {
    //省略
    const onClickDelete = (index) => {
        const newTodos = [...inCompleteTodos];
        newTodos.splice(index, 1)
        setInCompleteTodos(newTodos);
    }
    const onClickComplete = (index) => {
        const newInCompleteTodos = [...inCompleteTodos];
        newInCompleteTodos.splice(index, 1)
    }
    return ( 
        //省略
            <div className="incomplete-area">
              <p className="title">未完了のTODO</p>
              <ul>
                {inCompleteTodos.map((todo, index) => (
                        <li key={todo}>  
                            <div className="list-row">
                                <p className="todo-item">{todo}</p>
                                <button onClick={() => onClickComplete(index)}>完了</button>
                                <button onClick={() => onClickDelete(index)}>削除</button>
                            </div>
                        </li>                    
                    ))}
              </ul>
          </div>

後はstateを更新すれば完了ボタンを押した時に未完了TODOから削除されるようになる。
その前に完了TODOに追加する機能を実装する。

完了TODOに追加する

配列のstateを更新していくので新しくnewCompleteTodosという新しい完了TODOの一覧のstateを用意する。
これも既存の完了TODOの配列(completeTodos)に、完了ボタンが押された行の要素を追加することになるので、スプレッド構文でこれまでと同じように実装していく。

完了ボタンが押された行の要素の取得はinCompleteTodosindexを取得すれば良いので、以下のような記述になっていく。

Todo.jsx
export const Todo = () => {
    //省略
    const onClickComplete = (index) => {
        const newInCompleteTodos = [...inCompleteTodos];
        newInCompleteTodos.splice(index, 1)
    }

    const newCompleteTodos = [...completeTodos, inCompleteTodos[index]]; //ここを追加
    return ( 
        //省略

これで新しい完了TODOの一覧もできたので、未完了TODO(setInCompleteTodos)と完了TODO(setCompleteTodos)のそれぞれのstateを更新していく。

Todo.jsx
export const Todo = () => {
    //省略
    const onClickComplete = (index) => {
        const newInCompleteTodos = [...inCompleteTodos];
        newInCompleteTodos.splice(index, 1)

        const newCompleteTodos = [...completeTodos, inCompleteTodos[index]]; 
        setInCompleteTodos(newInCompleteTodos);
        setCompleteTodos(newCompleteTodos);
    }
    return ( 
        //省略

これで、タスクの追加・削除・完了の機能の実装ができた。

8.タスクの戻す機能の実装

完了ボタンの逆の流れを行う。
まずはmapの第二引数にindexを渡し、イベントの記述、onClickBackという関数を用意する。

Todo.jsx
export const Todo = () => {
    //省略
    const onClickComplete = (index) => {
        const newInCompleteTodos = [...inCompleteTodos];
        newInCompleteTodos.splice(index, 1)

        const newCompleteTodos = [...completeTodos, inCompleteTodos[index]]; 
        setInCompleteTodos(newInCompleteTodos);
        setCompleteTodos(newCompleteTodos);
    }

    const onClickBack = (index) => {
        
    }
    
    return ( 
        //省略
            <div className="complete-area">
              <p className="title">完了のTODO</p>
              <ul>
                  {completeTodos.map((todo, index) => (
                        <li key={todo}>
                              <div className="list-row">
                                  <p className="todo-item">{todo}</p>
                                  <button onClicK={() => onClickBack(index)}>戻す</button>
                              </div>   
                        </li>
                  ))}
              </ul>
          </div>
        </>

処理としては以下の通りなので順に行っていく

  • 戻すボタンを押すと完了TODOから削除される
  • 戻すボタンを押すと未完了TODOの一番下に追加される

戻すボタンを押すと完了TODOから削除される

Todo.jsx
export const Todo = () => {
    //省略
    
    const onClickBack = (index) => {
        const newCompleteTodos = [...completeTodos];
        newCompleteTodos.splice(index, 1);
    }
    
    return ( 
        //省略

戻すボタンを押すと未完了TODOの一番下に追加される

追加の処理を記述し、最後にそれぞれのstateを更新する。

Todo.jsx
export const Todo = () => {
    //省略
    
    const onClickBack = (index) => {
        const newCompleteTodos = [...completeTodos];
        newCompleteTodos.splice(index, 1);

        const newInCompleteTodos = [...inCompleteTodos, completeTodos(index)];
        setCompleteTodos(newCompleteTodos);
        setInCompleteTodos(newInCompleteTodos);
    }
    
    return ( 
        //省略

9.stateの初期値を削除する

TODOアプリに必要なすべての機能の実装ができたので、初期値として入れていた値を削除する。
最終的なコードは以下の通り。

Todo.jsx
import {useState} from "react";
import "./style.css";

export const Todo = () => {
    const [todoText, setTodoText] = useState("");
    const [inCompleteTodos, setInCompleteTodos] = useState([]);
    const [completeTodos, setCompleteTodos] = useState([]);
    
    const onChageTodoText = (event) => setTodoText(event.target.value);

    const onClickAdd = () => {
        if (todoText === "") return;
        const newTodos = [...inCompleteTodos, todoText];
        setInCompleteTodoText(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) => (
                    <li key={todo}>  
                        <div className="list-row">
                            <p className="todo-item">{todo}</p>
                            <button onClick={() => onClickComplete(index)}>完了</button>
                            <button onClick={() => onClickDelete(index)}>削除</button>
                        </div>
                    </li>                    
                ))}
              </ul>
          </div>
           <div className="complete-area">
              <p className="title">完了のTODO</p>
              <ul>
                  {completeTodos.map((todo, index) => (
                        <li key={todo}>
                              <div className="list-row">
                                  <p className="todo-item">{todo}</p>
                                  <button onClicK={() => onClickBack(index)}>戻す</button>
                              </div>   
                        </li>
                  ))}
              </ul>
          </div>
        </>
    );
};

10.コンポーネント化する

現在「入力エリア」「未完了エリア」「完了エリア」が同じファイルに記述されているので、これをコンポーネントで分割して可読性を向上する。


  • 入力エリア

ディレクトリを追加

srcフォルダ配下に新しくcomponentsというフォルダを作り、さらにその配下に「入力エリア」用のInputTodo.jsxというファイルを作成。

関数コンポーネントを記述

exportと関数コンポーネントInputoTodoを用意したら、Todo.jsxから該当部分を切り取って関数の中にペーストする。

InputTodo.jsx
export const InputTodo = () => {
    return (
        <div className="input-area">
            <input placeholder="TODOを入力" value={todoText} onChange={onChangeTodoText} />
            <button onClick={onClickAdd}>追加</button>
        </div>
    )
}

この状態だとtodoTextonChageTextなどの変数や関数が同ファイル内に存在しないのでエラーになっている。
そのためpropsで受け取ったものを参照できるようにリファクタリングしていく。

propsを受け取るようリファクタリング

propsを受け取る側であるInputTodo.jsxの関数コンポーネントの引数にpropsを渡す。

InputTodo.jsx
export const InputTodo = (props) => {
    return (
        <div className="input-area">
            <input placeholder="TODOを入力" value={todoText} onChange={onChangeTodoText} />
            <button onClick={onClickAdd}>追加</button>
        </div>
    )
}

そして呼び出し側のTodo.jsxでは切り取った部分にコンポーネントを呼び出し、Auto importでインポートしておく。

さらに関数もファイル内に存在していないためエラーが吐かれている。
この辺りもpropsで渡していく。
具体的には以下の3つをそれぞれTodo.jsx側でpropsで渡していく。

  • todoText
  • onChangeTodoText
  • onClickAdd

まずInputTodoコンポーネントの部分で、todoTextというprops名でtodoTextを渡す。
同様にonChangeTodoTextは省略してonChangeonClickAddは省略してonClickというprops名で、それぞれ呼び出す関数を渡していく。

Todo.jsx
import {useState} from "react";
import {InputTodo} from "./components/InpotTodo";
import "./style.css";

export const Todo = () => {
    const [todoText, setTodoText] = useState("");
    const [inCompleteTodos, setInCompleteTodos] = useState([]);
    const [completeTodos, setCompleteTodos] = useState([]);
    
    const onChageTodoText = (event) => setTodoText(event.target.value);

    const onClickAdd = () => {
        if (todoText === "") return;
        const newTodos = [...inCompleteTodos, todoText];
        setInCompleteTodoText(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={onChageTodoText} onClick={onClickAdd} />
         //省略
        </>
    );
};

これで呼び出し側のコードが完成したので、次はコンポーネント側で渡されたpropsを使えるようにリファクタリングしていく。

propsを使えるようにリファクタリング

オブジェクトの分割代入で、todoText,onChange,onClickの3つのpropsを取り出す。
そして名前を変えてpropsを使った部分に合わせて、InputTodo.jsxの変数や関数名を修正する。

InputTodo.jsx
export const InputTodo = (props) => {
    const {todoText, onChange, onClick} = props;
    return (
        <div className="input-area">
            <input placeholder="TODOを入力" value={todoText} onChange={onChange} /> //元はonChangeTodoText
            <button onClick={onClick}>追加</button> //元はonClickAdd
        </div>
    )
}

これでコンポーネントが完成。


  • 未完了エリア

ディレクトリを追加

InCompleteTodos.jsxというファイルを作成。

関数コンポーネントを記述

exportと関数コンポーネントInCompleteTodosを用意したら、Todo.jsxから該当部分を切り取って関数の中にペーストする。

InCompleteTodos.jsx
export const InCompleteTodos = () => {
    return (
        <div className="incomplete-area">
            <p className="title">未完了のTODO</p>
              <ul>
                {inCompleteTodos.map((todo, index) => (
                    <li key={todo}>  
                        <div className="list-row">
                            <p className="todo-item">{todo}</p>
                            <button onClick={() => onClickComplete(index)}>完了</button>
                            <button onClick={() => onClickDelete(index)}>削除</button>
                        </div>
                    </li>                    
                ))}
              </ul>
          </div>
    )
}

この状態だとinCompleteTodosonClickCompleteなどの変数や関数が同ファイル内に存在しないのでエラーになっている。
そのためpropsで受け取ったものを参照できるようにリファクタリングしていく。

propsを受け取るようリファクタリング

propsを受け取る側であるInCompleteTodos.jsxの関数コンポーネントの引数にpropsを渡す。

InCompleteTodos.jsx
export const InCompleteTodos = (props) => {
    return (
        <div className="incomplete-area">
            <p className="title">未完了のTODO</p>
              <ul>
                {inCompleteTodos.map((todo, index) => (
                    <li key={todo}>  
                        <div className="list-row">
                            <p className="todo-item">{todo}</p>
                            <button onClick={() => onClickComplete(index)}>完了</button>
                            <button onClick={() => onClickDelete(index)}>削除</button>
                        </div>
                    </li>                    
                ))}
              </ul>
          </div>
    )
}

そして呼び出し側のTodo.jsxでは切り取った部分にコンポーネントを呼び出し、Auto importでインポートしておく。

さらに関数もファイル内に存在していないためエラーが吐かれている。
この辺りもpropsで渡していく。
具体的には以下の3つをそれぞれTodo.jsx側でpropsで渡していく。

  • inCompleteTodos
  • onClickComplete
  • onClickDelete
Todo.jsx
import {useState} from "react";
import {InputTodo} from "./components/InpotTodo";
import {InCompleteTodos} from "./components/InCompleteTodos";
import "./style.css";

export const Todo = () => {
    const [todoText, setTodoText] = useState("");
    const [inCompleteTodos, setInCompleteTodos] = useState([]);
    const [completeTodos, setCompleteTodos] = useState([]);
    
    const onChageTodoText = (event) => setTodoText(event.target.value);

    const onClickAdd = () => {
        if (todoText === "") return;
        const newTodos = [...inCompleteTodos, todoText];
        setInCompleteTodoText(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={onChageTodoText} onClick={onClickAdd} />
          <InCompleteTodos todos={inCompleteTodos} onCkickComplete={onCkickComplete} onCkickDelete={onCkickDelete} /> 
         //省略
        </>
    );
};

これで呼び出し側のコードが完成したので、次はコンポーネント側で渡されたpropsを使えるようにリファクタリングしていく。

propsを使えるようにリファクタリング

オブジェクトの分割代入で、todos,onCkickComplet,onCkickDeleteの3つのpropsを取り出す。
そして名前を変えてpropsを使った部分に合わせて、InCompleteTodos.jsxの変数や関数名を修正する。

InCompleteTodos.jsx
export const InputTodo = (props) => {
    const {todos, onCkickComplet, onCkickDelete} = props;
    return (
        <div className="incomplete-area">
            <p className="title">未完了のTODO</p>
              <ul>
                {todos.map((todo, index) => (
                    <li key={todo}>  
                        <div className="list-row">
                            <p className="todo-item">{todo}</p>
                            <button onClick={() => onClickComplete(index)}>完了</button>
                            <button onClick={() => onClickDelete(index)}>削除</button>
                        </div>
                    </li>                    
                ))}
              </ul>
          </div>
    )
}

これでコンポーネントが完成。


  • 完了エリア

ディレクトリを追加

CompleteTodos.jsxというファイルを作成。

関数コンポーネントを記述

exportと関数コンポーネントCompleteTodosを用意したら、Todo.jsxから該当部分を切り取って関数の中にペーストする。

CompleteTodos.jsx
export const CompleteTodos = () => {
    return (
         <div className="complete-area">
              <p className="title">完了のTODO</p>
              <ul>
                  {completeTodos.map((todo, index) => (
                        <li key={todo}>
                              <div className="list-row">
                                  <p className="todo-item">{todo}</p>
                                  <button onClicK={() => onClickBack(index)}>戻す</button>
                              </div>   
                        </li>
                  ))}
              </ul>
          </div>
    )
}

この状態だとCompleteTodosonClickBackの変数や関数が同ファイル内に存在しないのでエラーになっている。
そのためpropsで受け取ったものを参照できるようにリファクタリングしていく。

propsを受け取るようリファクタリング

propsを受け取る側であるInCompleteTodos.jsxの関数コンポーネントの引数にpropsを渡す。

CompleteTodos.jsx
export const CompleteTodos = (props) => {
    return (
         <div className="complete-area">
              <p className="title">完了のTODO</p>
              <ul>
                  {completeTodos.map((todo, index) => (
                        <li key={todo}>
                              <div className="list-row">
                                  <p className="todo-item">{todo}</p>
                                  <button onClicK={() => onClickBack(index)}>戻す</button>
                              </div>   
                        </li>
                  ))}
              </ul>
          </div>
    )
}

そして呼び出し側のTodo.jsxでは切り取った部分にコンポーネントを呼び出し、Auto importでインポートしておく。

さらに関数もファイル内に存在していないためエラーが吐かれている。
この辺りもpropsで渡していく。
具体的には以下の3つをそれぞれTodo.jsx側でpropsで渡していく。

  • CompleteTodos
  • onClickBack
Todo.jsx
import {useState} from "react";
import {InputTodo} from "./components/InpotTodo";
import {InCompleteTodos} from "./components/InCompleteTodos";
import {CompleteTOdos} form "./components/CompleteTodos";
import "./style.css";

export const Todo = () => {
    const [todoText, setTodoText] = useState("");
    const [inCompleteTodos, setInCompleteTodos] = useState([]);
    const [completeTodos, setCompleteTodos] = useState([]);
    
    const onChageTodoText = (event) => setTodoText(event.target.value);

    const onClickAdd = () => {
        if (todoText === "") return;
        const newTodos = [...inCompleteTodos, todoText];
        setInCompleteTodoText(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={onChageTodoText} onClick={onClickAdd} />
          <InCompleteTodos todos={inCompleteTodos} onCkickComplete={onCkickComplete} onCkickDelete={onCkickDelete} /> 
          <CompleteTodos todos={CompleteTodos} onClickBack={onClickBack} />
        </>
    );
};

これで呼び出し側のコードが完成したので、次はコンポーネント側で渡されたpropsを使えるようにリファクタリングしていく。

propsを使えるようにリファクタリング

オブジェクトの分割代入で、todos,onBackの2つのpropsを取り出す。
そして名前を変えてpropsを使った部分に合わせて、CompleteTodos.jsxの変数や関数名を修正する。

CompleteTodos.jsx
export const InputTodo = (props) => {
    const {todos, onCkickBack = props;
    return (
        <div className="incomplete-area">
            <p className="title">未完了のTODO</p>
              <ul>
                {todos.map((todo, index) => (
                    <li key={todo}>  
                        <div className="list-row">
                            <p className="todo-item">{todo}</p>
                            <button onClick={() => onClickComplete(index)}>完了</button>
                            <button onClick={() => onClickDelete(index)}>削除</button>
                        </div>
                    </li>                    
                ))}
              </ul>
          </div>
    )
}

これでコンポーネントが完成。

####最終的なTodo.jsx
親コンポーネントのAppreturnの中身が3つのコンポーネントのエリアに分かれていて、各コンポーネントの役割が名前からわかる用の異なった。
さらにコンポーネントしておくことによって、使いまわしたいときに1行のコピペだけで済むようになった。

Todo.jsx
import {useState} from "react";
import {InputTodo} from "./components/InpotTodo";
import {InCompleteTodos} from "./components/InCompleteTodos";
import {CompleteTOdos} form "./components/CompleteTodos";
import "./style.css";

export const Todo = () => {
    const [todoText, setTodoText] = useState("");
    const [inCompleteTodos, setInCompleteTodos] = useState([]);
    const [completeTodos, setCompleteTodos] = useState([]);
    
    const onChageTodoText = (event) => setTodoText(event.target.value);

    const onClickAdd = () => {
        if (todoText === "") return;
        const newTodos = [...inCompleteTodos, todoText];
        setInCompleteTodoText(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={onChageTodoText} onClick={onClickAdd} />
          <InCompleteTodos todos={inCompleteTodos} onCkickComplete={onCkickComplete} onCkickDelete={onCkickDelete} /> 
          <CompleteTodos todos={CompleteTodos} onClickBack={onClickBack} />
        </>
    );
};

11.コンポーネントに該当するcssのスタイルもコンポーネントで分ける

今回はReactが用意しているインラインスタイルでコンポーネント化する。
またInputoTodoコンポーネント以外のコンポーネントはclassを使いまわしている部分があるので、今回はInputoTodoコンポーネントのスタイルのみ格納する。

各コンポーネントのclassNameは削除し、インラインスタイルでスタイルを記述していく。
可読性を考慮し一度変数に挟む形でコンポーネントに格納していく。

判定式はisMaximiIncompleteTodosという変数にすべて格納することで、仕様変更があった場合でも簡単に変更できるようにしておく

InputTodo.jsx
const style = {
    backgroundColor: "#c6e5d9",
    width: "400px",
    height: "30px",
    padding: "8px",
    margin: "8px",
    borderRadius: "8px",
}

export const InputTodo = (props) => {
    const {todoText, onChange, onClick} = props;
    return (
        <div className="input-area">
            <input placeholder="TODOを入力" value={todoText} onChange={onChange} /> //元はonChangeTodoText
            <button onClick={onClick}>追加</button> //元はonClickAdd
        </div>
    )
}

12.Todoの上限設定

InputoTodoInCompleteTodosの間にテキストを表示する

タグを用意。
そしてincompleteTodosの配列の長さが5個以上の時だけ表示させたいので、{}の中に論理演算子で判定処理を記述。

Todo.jsx
import { useState } from "react";
import { InputTodo } from "./components/InputTodo";
import { IncompleteTodos } from "./components/IncompleteTodos";
import "./styles.css";
import { CompleteTodos } from "./components/CompleteTodos";

export const Todo = () => {
  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 targetTodo = incompleteTodos[index]; // spliceする前に取得
    const newIncompleteTodos = [...incompleteTodos];
    newIncompleteTodos.splice(index, 1);

    const newCompleteTodos = [...completeTodos, targetTodo]; // ここでtargetTodoを使う
    setIncompleteTodos(newIncompleteTodos);
    setCompleteTodos(newCompleteTodos);
  };

  const onClickBack = (index) => {
    const newCompleteTodos = [...completeTodos];
    newCompleteTodos.splice(index, 1);

    const newIncompleteTodos = [...incompleteTodos, completeTodos[index]];
    setCompleteTodos(newCompleteTodos);
    setIncompleteTodos(newIncompleteTodos);
  };

  const isMaximiIncompleteTodos = incompleteTodos.length >= 5;

  return (
    <>
      <InputTodo
        todoText={todoText}
        onChange={onChangeTodoText}
        onClick={onClickAdd}
        disabled={isMaximiIncompleteTodos}
      />
      {isMaximiIncompleteTodos && (
        <p style={{ color: "red" }}>
          上限に達しました。タスクを消化してください。
        </p>
      )}
      <IncompleteTodos
        todos={incompleteTodos}
        onClickComplete={onClickComplete}
        onClickDelete={onClickDelete}
      />
      <CompleteTodos todos={completeTodos} onClickBack={onClickBack} />
    </>
  );
};

そしてInputTodoコンポーネントのinputbuttondisabledという記述を追加することで、条件に達したときに機能できないようにする。

InputTodo.jsx
const style = {
  backgroundColor: "#c6e5d9",
  width: "400px",
  height: "30px",
  padding: "8px",
  margin: "8px",
  borderRadius: "8px",
};

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>
  );
};

以上でReactを使ったTODOアプリの制作が完了。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?