React Hooks、関数コンポーネントを使ってみた

関数コンポーネント、ReactHooksについて調べ、触ってみてわかったことをまとめます。

## 背景
- 以前はクラスコンポーネントが主流だった
- クラスコンポーネントはいろんなことができる故に、コードがカオスになりがち
- 現在はより関数コンポーネントが主流
- 関数コンポーネントとHooksを組み合わせることで、程よくReactの機能にアクセスできるようになる

## 注意点など
- Hooksは全て`useHogehoge`という名前
- 必ず関数の中のトップレベルで使う
    - 要はif文やfor文の中で使ってはいけない
    - いつ何時でも、同じ回数同じ順番で実行される必要がある。
    - 基本的に関数の一番上に並べておけばOK

## よく使うHooks
- `useState`
    - stateの読み書きに使う
- `useEffect`
    - ライフサイクルメソッドの代替

## 関数コンポーネント + HooksでTodoアプリを作ってみる
### セットアップ
```zsh
$ create-react-app react-todo
$ cd react-todo
$ rm -rf src
$ mkdir src src/components
$ cd src
$ touch index.js components/App.js components/AddTodo.js components/TodoElement.js
```

### 実装
```jsx:src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);
```
#### コンポーネントを分割しない場合
```jsx:src/components/App.js
import React, { useState } from "react";

const App = () => {
  const [value, setValue] = useState("");
  const [todoList, setTodoList] = useState([]);

  const handleChange = (e) => {
    const newValue = e.target.value;
    setValue(newValue);
  };

  const add = () => {
    const newTodo = { id: todoList.length, content: value };
    const newTodoList = [...todoList, newTodo];
    setTodoList(newTodoList);
    setValue("");
  };

  const handleDelete = (id) => {
    const newTodoList = todoList.filter((todo) => todo.id !== id);
    setTodoList(newTodoList);
  };

  return (
    <div>
      <h1>TODO App</h1>
      <input type="text" value={value} onChange={handleChange} />
      <button onClick={add}>追加</button>
      <ul>
        {todoList.map((todo) => (
          <li key={todo.id}>
            {todo.content}
            <button onClick={() => handleDelete(todo.id)}>削除</button>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default App;

```

#### コンポーネントを分割する場合
```jsx:src/components/App.js
import React, { useState } from "react";
import TodoElement from "./TodoElement";
import AddTodo from "./AddTodo";

const App = () => {
  const [value, setValue] = useState("");
  const [todoList, setTodoList] = useState([]);

  const handleChange = (e) => {
    const newValue = e.target.value;
    setValue(newValue);
  };

  const add = () => {
    const newTodo = { id: todoList.length, content: value };
    const newTodoList = [...todoList, newTodo];
    setTodoList(newTodoList);
    setValue("");
  };

  const handleDelete = (id) => {
    const newTodoList = todoList.filter((todo) => todo.id !== id);
    setTodoList(newTodoList);
  };

  return (
    <div>
      <h1>TODO App</h1>
      <AddTodo value={value} onChange={handleChange} add={add} />
      <ul>
        {todoList.map((todo) => (
          <TodoElement
            key={todo.id}
            content={todo.content}
            onDelete={() => handleDelete(todo.id)}
          />
        ))}
      </ul>
    </div>
  );
};

export default App;
```
```jsx:src/components/AddTodo.js
import React from "react";

const AddTodo = (props) => {
  return (
    <>
      <input type="text" value={props.value} onChange={props.onChange} />
      <button onClick={props.add}>追加</button>
    </>
  );
};

export default AddTodo;
```
```jsx:src/components/TodoElement.js
import React from "react";

const TodoElement = (props) => {
  return (
    <li>
      {props.content}
      <button onClick={props.onDelete}>削除</button>
    </li>
  );
};

export default TodoElement;
```

### ポイント
- render関数はpureな関数であるべき
    - stateの変更が起こるとrender関数が呼ばれるようになっているので無限ループになってしまう
    - render関数内でstateを変更してはいけない
- 関数コンポーネントで実装すると、thisを使って内部のメソッドや変数にアクセスする必要がないので、thisの挙動を意識せずに済む
- `onChange`などのイベントハンドラでは、関数を渡すと引数も勝手に渡してくれる
- `state`はできるだけ一箇所で管理する
    - 子コンポーネント内で変更があったときはpropsの関数を実行して親で処理を行う
- propsは`on〇〇`という名前が一般的なのに対して、そのコールバック関数は`handle〇〇`という名前が一般的

## 参考
- [最近Reactを始めた人向けのReact Hooks入門](https://sbfl.net/blog/2019/11/12/react-hooks-introduction/)
- [【最新版・hooks対応】 Reactの基礎を学んでTODOアプリを作ろう！｜宮川 竜太朗｜note](https://note.com/dragontaro/n/n04e0b9c0cca7)