JavaScriptのUIライブラリ ReactのHook使用してToDoアプリを作成してみました

# はじめに
この記事ではJavaScriptのライブラリであるReactとReactの機能であるhookを使用して簡単なToDoアプリの実装を行います。
以前に書いた記事をベースに書き換えるので、そちらも参考にしてみてください。
[JavaScriptのUIライブラリ ReactでToDoアプリを作成してみました](https://qiita.com/penpenta/items/4ebe2f881533d2510b9b)
Reactの[ドキュメント](https://ja.reactjs.org/docs/getting-started.html)や[チュートリアル](https://ja.reactjs.org/tutorial/tutorial.html)（三目並べ）を一通り行った後の練習になるように書きたいと思います。
環境構築に関しては、`create-react-app`を使用して作成しています。
環境構築に関しては以前書いた[記事](https://qiita.com/penpenta/items/2fa98aa885ba8fa7ed90)があります。
[ソースコード](https://github.com/oq-Yuki-po/react-tutorial/tree/todo-app-pure-css-hoook)

## 目次
1. コンポーネントの確認
2. 各コンポーネントの解説
3. まとめ

## 1. コンポーネントの確認
すぐに動かせる環境を置いておきます。
<p class="codepen" data-height="265" data-theme-id="default" data-default-tab="html,result" data-user="oq-yuki-po" data-slug-hash="RwNyEOW" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="ReactToDo with Hook">
  <span>See the Pen <a href="https://codepen.io/oq-yuki-po/pen/RwNyEOW">
  ReactToDo with Hook</a> by oq-Yuki-po (<a href="https://codepen.io/oq-yuki-po">@oq-yuki-po</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>
ReactはUIのパーツをコンポーネントという独立した一つの部品とみなして構成していきます。
今回の例では下記の様に分割しました。
以前は、これらのコンポーネントを全てクラスで定義して行きましたが今回は関数コンポーネントで定義して行きます。

![ReactToDoApp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/190554/895b5cc4-a1d9-2403-8daf-16de0d763e06.png)

ToDoアプリケーションを構成するコンポーネントは全部で4つあります。

* ToDo
ToDoアプリケーションの全体を表します

* TaskAdd
新しいタスクの追加を行います

* TaskList
追加されたタスクをリストにして表示します

* TaskItem
一つのタスクを表します

## 2. 各コンポーネントの解説

### 2-1. ToDo.js

#### 2-1-1. ソース全体

```jsx:ToDo.js
import React, { useState, useReducer } from "react";
import TaskAdd from './TaskAdd';
import TaskList from './TaskList';

function reducer(state, action) {
  switch (action.type) {
    case 'add':
      return [
        ...state,
        action.NewTask
      ];
    case 'delete':
      let TaskIndex = 0;
      for (var i = 0; i < state.length; i++) {
        if (state[i].key.toString() === action.TaskId.toString()) {
          TaskIndex = i;
        }
      }
      return state.filter((_, index) => index !== TaskIndex);
    default:
      return state;
  }
}

function ToDo() {
  const [TaskId] = useState(0)
  const [ToDoList, dispatch] = useReducer(reducer, [])
  return (
    <main className='todo-component'>
      <TaskAdd id={TaskId} dispatch={dispatch} TaskList={ToDoList}/>
      <TaskList TaskList={ToDoList} />
    </main>
  );
}

export default ToDo; 
```

#### 2-1-2. モジュールのインポート

```javascript:import
import React, { useState, useReducer } from "react";
import TaskAdd from './TaskAdd';
import TaskList from './TaskList';
```

`useState`と`useReducer`がhookと呼ばれるものです。
これを使用することで、クラスコンポーネントで行なっていた状態管理が関数コンポーネントでも行えます。

#### 2-1-3. ToDoコンポーネント

```jsx:ToDoコンポーネント
function ToDo() {
  
  const [TaskId] = useState(0)
  const [ToDoList, dispatch] = useReducer(reducer, [])

  return (
    <main className='todo-component'>
      <TaskAdd id={TaskId} dispatch={dispatch} TaskList={ToDoList}/>
      <TaskList TaskList={ToDoList} />
    </main>
  );
}
```

クラスで定義していた際は、constructorを定義していたと思います。
[以前の記事](https://qiita.com/penpenta/items/4ebe2f881533d2510b9b#2-2-3-state%E3%81%AE%E5%AE%9A%E7%BE%A9%E3%81%A8%E9%96%A2%E6%95%B0%E3%81%AE%E7%99%BB%E9%8C%B2)では、下記の様に定義していました。

```javascript:ToDoコンポーネントのconstructor
constructor(props) {
  super(props);
  this.state = {
    TaskList: [],
    TaskId: 0
  };
  this.deleteTask = this.deleteTask.bind(this);
  this.addTask = this.addTask.bind(this);
}
```

関数でコンポーネントを定義する際は以下の様になります。

```javascript:ToDoコンポーネント(クラス版)
const [TaskId] = useState(0)
const [ToDoList, dispatch] = useReducer(reducer, [])
```

stateを定義する際に`useState`を使用します。
`const [状態管理したい変数, 状態を変更する関数] = useState(状態管理したい変数の初期値)`
TaskIdは子コンポーネントに渡して、そちらで管理するので変更する関数を定義していません。
（定義してる場所が、そもそもどうなの？みたいなツッコミはご勘弁を。後々にAPIとの連携を見越した実装だと解釈お願いします。。。）
`const [状態管理したい変数, 状態を変更する関数] = useState(初期値)`
配列や複雑なロジックをstateに持たせる時は、TaskListの様に`useReducer`を使用します。
`const [状態管理したい変数, reducerで定義した関数(dispatch)] = useReducer(reducer, 状態管理したい変数の初期値);`
公式の引用
>通常、useReducer が useState より好ましいのは、複数の値にまたがる複雑な state ロジックがある場合や、
>前の state に基づいて次の state を決める必要がある場合です。また、useReducer を使えば
>コールバックの代わりに dispatch を下位コンポーネントに渡せるようになるため、
>複数階層にまたがって更新を発生させるようなコンポーネントではパフォーマンスの最適化にもなります。

引用にも書いてある通り、`<TaskAdd id={TaskId} dispatch={dispatch} TaskList={ToDoList}/>`
dispatchを`TaskAdd`に渡しています。


#### 2-1-4. reducerの定義

```javascript:reducerの定義
function reducer(state, action) {
  switch (action.type) {
    case 'add':
      return [
        ...state,
        action.NewTask
      ];
    case 'delete':
      let TaskIndex = 0;
      for (var i = 0; i < state.length; i++) {
        if (state[i].key.toString() === action.TaskId.toString()) {
          TaskIndex = i;
        }
      }
      return state.filter((_, index) => index !== TaskIndex);
    default:
      return state;
  }
}
```

reducerの定義を行います。
`state`は状態管理したい変数、今回はTaskListが入っています。
`action` は`dispatch`で、この関数を実行するときに指定したパラメータが格納されています。
`action.type`で、どの操作なのかを判定するのに使用しています。
`add`や`delete`はそれぞれ、TaskListに新規にタスクを追加、指定したタスクを削除の処理を行なっています。

### 2-2. TaskAdd.js

#### 2-2-1. ソース全体

```jsx:TaskAdd.js
import React, { useState } from "react";
import Task from './Task'

function TaskAdd(props) {

    const [NewTask, setTask] = useState('')
    const [TaskId, setTaskId] = useState(props.id)
    const [ErrorMessage, setErrorMessage] = useState('')

    function handleClick() {
        if (NewTask === '') {
            setErrorMessage('入力が空です。')
            return 0
        }
        for (var i = 0; i < props.TaskList.length; i++) {
            if (props.TaskList[i].props.name === NewTask) {
                setErrorMessage('タスク名が重複しています。')
                return 0
            }
        }
        props.dispatch({
            type: 'add',
            NewTask: <Task key={TaskId} id={TaskId} name={NewTask} dispatch={props.dispatch} />
        })
        setTaskId(TaskId + 1)
        setTask('')
        setErrorMessage('')
    }

    return (
        <section className='task-creator'>
            <h2>Task Add</h2>
            <input className='task-item-text' type="text" placeholder="Task" value={NewTask}
                onChange={(e) => setTask(e.target.value)} />
            <button className='task-add-btn' type="button" onClick={handleClick}>
                Add
                </button>
            <p className='error-msg'>{ErrorMessage}</p>
        </section>
    )
}

export default TaskAdd; 
```

#### 2-2-2. stateの定義

```javascript:stateの定義
const [NewTask, setTask] = useState('')
const [TaskId, setTaskId] = useState(props.id)
const [ErrorMessage, setErrorMessage] = useState('')
```

Task.jsと同じ様に定義しています。
`const [状態管理したい変数, 状態を変更する関数] = useState(状態管理したい変数の初期値)`

#### 2-2-3. handleClick

```javascript:TaskAdd.jsのhandleClick
function handleClick() {
    if (NewTask === '') {
        setErrorMessage('入力が空です。')
        return 0
    }
    for (var i = 0; i < props.TaskList.length; i++) {
        if (props.TaskList[i].props.name === NewTask) {
            setErrorMessage('タスク名が重複しています。')
            return 0
        }
    }
    props.dispatch({
        type: 'add',
        NewTask: <Task key={TaskId} id={TaskId} name={NewTask} dispatch={props.dispatch} />
    })
    setTaskId(TaskId + 1)
    setTask('')
    setErrorMessage('')
}
```

定義したstateにアクセスする際は`this.state.NewTask`の様に行なっていましたが
hookでは単純に`NewTask`でアクセスできます。(this.stateの呪縛から開放される！！)

stateの更新の際は`this.setState({ ErrorMessage: '入力が空です。' })`ではなく
state定義時の関数をそのまま使用できます。
つまり`setErrorMessage('入力が空です。')`と書けます。

タスクの追加時にはTask.jsからpropsとして受け取ってあるdispatchを使用しています。

```
props.dispatch({
    type: 'add',
    NewTask: <Task key={TaskId} id={TaskId} name={NewTask} dispatch={props.dispatch} />
})
```

`type`と`NewTask`はactionで取れる様に追加しています。

#### 2-2-4. render

```jsx:TaskAdd.jsのrender
return (
    <section className='task-creator'>
        <h2>Task Add</h2>
        <input className='task-item-text' type="text" placeholder="Task" value={NewTask}
            onChange={(e) => setTask(e.target.value)} />
        <button className='task-add-btn' type="button" onClick={handleClick}>
            Add
            </button>
        <p className='error-msg'>{ErrorMessage}</p>
    </section>
)
```

`this.state`で取得することが無くなったので、少しスッキリしたと思います。

### 2-3. Task.js

#### 2-3-1. ソース全体

```jsx:Task.js
import React, { useState } from "react";

function Task(props) {

    const [isDone, setStatus] = useState(props.isDone)
    const task_id = 'task-id-' + props.id.toString()
    const css_label = 'task-item-label'
    const css_isDone = `${css_label} isDone`
    const css_Wip = `${css_label} WorkInProgress`

    return (
        <li className='task-item-row'>
            <input id={task_id} className='task-item-checkbox' type='checkbox'
                onChange={() => (isDone === true) ? setStatus(false) : setStatus(true)}>
            </input>
            <label htmlFor={task_id} className={(isDone === true) ? css_isDone : css_Wip}>
                {props.name}
            </label>
            <i className="material-icons icon" onClick={() => props.dispatch({type:'delete', TaskId:props.id})}>
                delete
            </i>
        </li>
    );
}
export default Task; 
```

#### 2-3-2. stateの定義

```javascript:stateと定数の定義
const [isDone, setStatus] = useState(props.isDone)
const task_id = 'task-id-' + props.id.toString()
const css_label = 'task-item-label'
const css_isDone = `${css_label} isDone`
const css_Wip = `${css_label} WorkInProgress`
```

stateとclassNameの定数を定義しているのみです。

#### 2-3-3. render

```jsx:Task.js
return (
    <li className='task-item-row'>
        <input id={task_id} className='task-item-checkbox' type='checkbox'
            onChange={() => (isDone === true) ? setStatus(false) : setStatus(true)}>
        </input>
        <label htmlFor={task_id} className={(isDone === true) ? css_isDone : css_Wip}>
            {props.name}
        </label>
        <i className="material-icons icon" onClick={() => props.dispatch({type:'delete', TaskId:props.id})}>
            delete
        </i>
    </li>
);

```

`onChange={() => (isDone === true) ? setStatus(false) : setStatus(true)}`で単純なif文を省略しています。
`onClick={() => props.dispatch({type:'delete', TaskId:props.id})}`でpropsで受け取ったdispatchを使用してタスクの削除を行なっています。

### 2-4. TaskList.js

```jsx:TaskList.js
import React from "react";

function TaskList(props) {
    return (
        <section className='task-list'>
            <h2>Task List</h2>
            <ul>
                {props.TaskList}
            </ul>
        </section>
    );
}

export default TaskList; 
```

特にクラスと関数で違いは無いでしょうか、強いて言えばクラスで書くより短いくらいでしょうか？？

## 3. まとめ
 Reactのhookを使用して、クラスコンポーネントを関数コンポーネントのみで書き換えてみました。
 スッキリ書き換えられるのはメリットに感じました。
 ドキュメントを読む限りでは、完全に互換性がある訳では無いそうなので更に勉強が必要そうです。。。
 最後まで見てくださり、ありがとうございます。
 質問や、指摘は大歓迎ですので、よろしくお願いします。