Reactを再考する#1

## きっかけ

前回の[記事](https://qiita.com/a2-tsuchiya/items/0435accb6a6dd61c5826)を書いてて、改めてReactを掘り下げてみようかと。。
Hook APIをメインに広く浅〜くご紹介していこうと思います。
第一回は、`useState`と`useRef`です。

## 前提

- React 16.8〜(HookAPI前提)
- Reactの基本的な説明はしません
- TypeScriptで書いてます

## お題

色の名前とカラーコードを登録する簡単なフォームを作ります。
２パターン考えてみます。

***./src/App.tsx***

```tsx
import { useState } from "react"
import colorData from "./color-data.json";
import UncontrolledForm from "./UncontrolledForm"
import ControlledForm from "./ControlledForm"

interface Color {
  title: string,
  color: string
}

export default function App() {
  const [colors, setColors] = useState<Color[]>(colorData);

  const addColor = (title: string, color: string) => {
    const newColors = [...colors, { title, color }]
    setColors(newColors)
  }
  return (
    <UncontrolledForm
      sendParent={(title, color) => addColor(title, color)}
    />
    <ControlledForm
      onNewColor={(title, color) => addColor(title, color)}
    />
  );
}
```

***パターンA: ./src/UncontrolledForm.tsx***

```tsx
import { useRef } from "react"

interface Props {
  sendParent: (title: string, color: string) => void;
}

export default function UncontrolledForm({ sendParent }: Props) {
  const txtTitle = useRef<HTMLInputElement>(null);
  const hexColor = useRef<HTMLInputElement>(null);

  const submit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    /** validate */
    if (!txtTitle || !txtTitle.current) return;
    if (!hexColor || !hexColor.current) return;

    const title = txtTitle.current.value;
    const color = hexColor.current.value;
    /** notify parent component */
    sendParent(title, color);
    /** reset input values */
    txtTitle.current.value = "";
    hexColor.current.value = "";
  };

  return (
    <form onSubmit={submit}>
      <input ref={txtTitle} type="text" placeholder="color title..." required />
      <input ref={hexColor} type="color" required />
      <button>ADD</button>
    </form>
  );
}
```
***パターンB: ./src/ControlledForm.tsx***

```tsx
import { useState } from "react"

interface Props {
  sendParent: (title: string, color: string) => void;
}

export default function ControlledForm({ sendParent }: Props) {
  const [title, setTitle] = useState("");
  const [color, setColor] = useState("#000000");

  const submit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    sendParent(title, color);
    setTitle("");
    setColor("");
  };

  return (
    <form onSubmit={submit}>
      <input
        value={title}
        onChange={(event) => setTitle(event.target.value)}
        type="text"
        placeholder="color title..."
        required
      />
      <input
        value={color}
        onChange={(event) => setColor(event.target.value)}
        type="color"
        required
      />
      <button>ADD</button>
    </form>
  );
}
```

### 一応説明する

- **useState**
    - データが更新されると、自身がフックされたコンポーネントを、更新されたデータで再描画するフック
- **useRef**
    - 描画されたコンポーネントのDOMノードへの参照を保持するフック

### どちらで書くべき？

結果は同じです。違いは```submit```関数の中身です。

- ***パターンA: UncontrolledForm***
    - 処理手順を列挙していて命令型っぽい
    - 副作用がある（DOMノードのvalue属性を書き換えている）
    - **制御されていないコンポーネント**である
- ***パターンB: ControlledForm***
    - 宣言型っぽい
    - 副作用がない（DOMノードに直接アクセスしていない）
    - **制御されたコンポーネント**である


そうです、Reactは**関数型プログラミング**の影響を強く受けています。
関数型？と思った方は[前回の記事](https://qiita.com/a2-tsuchiya/items/0435accb6a6dd61c5826)をご覧くださいましw

React界隈では、

- **制御されていないコンポーネント**
    - DOMを介してデータにアクセスするコンポーネント
- **制御されたコンポーネント**
    - React（のステート）によってデータが管理されるコンポーネント

と区別され、**制御されたコンポーネント**のアプローチが**推奨**されているようです。。
(もちろん```useRef```を使うケースもあります)


### カスタムフック

さて、```useState```を使ってデータを管理した方が良いことは分かりました。
もうちょっとカッコよくしてみます。

言葉の通り、既存のフックをラップしたり、独自のフックを作ったりできる機能です。
今回は```<input>```タグへの```value```の割り当てと更新（```onChange```イベント）を抽象化してみます。

```tsx
value={title}
onChange={(event) => setTitle(event.target.value)}
```

React書いている方は分かると思いますが、
この手のフォーム入力とステート管理のパターンは腐るほど出てきますよねw

***./src/hook.tsx***

```tsx
import { useState } from "react";

type Hook = (
  initialValue: string
) => [
  {
    value: string;
    onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
  },
  () => void
];

export const useInput: Hook = (initialValue) => {
  const [value, setValue] = useState(initialValue);
  return [
    {
      value,
      onChange: (e) => setValue(e.target.value)
    },
    () => setValue(initialValue)
  ];
};
```

***./src/FormWithCustomHook.tsx***

```tsx
import { useInput } from "./hook";

export default function FormWithCustomHook({ sendParent }: Props) {
  const [titleProps, resetTitle] = useInput("");
  const [colorProps, resetColor] = useInput("#000000");

  const submit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    sendParent(titleProps.value, colorProps.value);
    resetTitle();
    resetColor();
  };

  return (
    <form onSubmit={submit}>
      <input
        {...titleProps}
        type="text"
        placeholder="color title..."
        required
      />
      <input {...colorProps} type="color" required />
      <button>ADD</button>
    </form>
  );
}
```

いい感じですね。。

- 重複するコードがなくなった
- 関数がより抽象化され、何をしているかが明確
- スプレッド構文で見た目もスッキリ

カスタムフックは使いこなすと可読性がかなり向上すると思います。

## 次回

親->子コンポーネントに```props```を経由して変数やら関数やらを渡すのって、、、

##### ぶっちゃけ面倒じゃないですかwww

(筆者はかなり早い段階でそう思いました)
しかもTypeScriptで書いていると、型定義やらなんやらでコード量が増えるのなんのって。。

いわゆる**Propsバケツリレー**というやつでして、今回の例では気になりませんが
コンポーネントのネストが深くなると**カオス**なことになりますw
もっとグローバル（**汚染せず**）に扱えるステートはないのか、、、

ということで、次回は**useContext**です！
個人的に工夫の余地がたくさんあってかなり面白い機能だと思います。。

## 参考文献

[Reactハンズオンラーニング 第2版 ――Webアプリケーション開発のベストプラクティス](https://www.oreilly.co.jp/books/9784873119380/)