Reactを再考する#2

## まえがき

[第一回](https://qiita.com/a2-tsuchiya/items/7a827b0b5487c9db2b75)に続いてReactを掘り下げてみようシリーズです。
第二回は、**useContext**です

## 前提

- React 16.8~
- Reactの基本的な説明はしません
- TypeScriptで書いてます

## なぜuseContext？

前回の```useState```と比較してみます。

- ```useState```
    - ステートをルートコンポーネントで一元管理するのは限界がある
    - propsバケツリレーがとにかく面倒
- ```useContext```
    - propsを経由せずにコンポーネント間でステートを伝達できる
    - 工夫次第でコンテキストを気にせずデータを共有できる

## とりあえず使ってみる

**./src/index.tsx**

```tsx
import React, { createContext } from "react";
import { render } from "react-dom";
import App from "./App";
import colors from "./color-data.json"

interface Color {
  title: string
  color: string
}
export const ColorContext = createContext([] as Color[])

render(
  <ColorContext.Provider value={colors}>
    <App />
  </ColorContext.Provider>,
  document.getElementById("root")
);
```

**./src/App.tsx**

```tsx
import React, { useContext } from "react";
import { ColorContext } from "./index"

export default function App() {
  const colors = useContext(ColorContext)
  return (
    <ul>
      {colors.map((color, i) => (
        <li key={i}>{color.title}</li>
      ))}
    </ul>
  );
}
```

propsを経由せずに```colors```変数を子コンポーネントに渡せてますね。
ネストが深くても、そのコンポーネントから```useContext```を呼び出せばいいのでpropsバケツリレーからも開放されます。

## Store的に使う

個人的にお気に入りの手法ですw
[Redux](https://redux.js.org/api/store)にStoreという概念がありますが、こっちの方が全然ラクだと思います。

**./src/ColorProvider.tsx**

```tsx
import React, { createContext, useContext, useState } from "react";
import colorData from "./color-data.json";

interface IColor {
  title: string;
  color: string;
}
interface ColorContextType {
  colors: IColor[];
  addColor: (title: string, color: string) => void;
}

const ColorContext = createContext({} as ColorContextType);
/** ポイント①: コンテキストをカスタムフックとしてエクスポートする */
export const useColors = () => useContext(ColorContext);

const ColorProvider: React.FC = ({ children }) => {
  const [colors, setColors] = useState(colorData);

  /** ポイント②: ステートの用途を限定して公開する */
  const addColor = (title: string, color: string) => {
    setColors([ ...colors, { title, color } ]);
  };
  return (
    <ColorContext.Provider value={{ colors, addColor }}>
      {children}
    </ColorContext.Provider>
  );
};
export default ColorProvider;
```

**./src/index.tsx**

```tsx
import React from "react";
import { render } from "react-dom";
import ColorPrivider from "./ColorPrivider";
import App from "./App";

/** Appコンポーネント配下全てでコンテキストが利用できる */
render(
  <ColorPrivider>
    <App />
  </ColorPrivider>,
  document.getElementById("root")
);
```

**./src/App.tsx**

```tsx
import React, { useContext } from "react";
import { useColors } from "./ColorProvider"
import FormWithCustomHook from "./FormWithCustomHook"

export default function App() {
  /** コンテキストからデータを取得 */
  const { colors } = useColors()
  return (
    <>
      <FormWithCustomHook />
      <ul>
        {colors.map((color, i) => (
          <li key={i}>{color.title}</li>
        ))}
      </ul>
    </>
  );
}
```

**./src/FormWithCustomHook.tsx**（[前回](https://qiita.com/drafts)の例を流用）

```tsx
import React from "react";
import { useInput } from "./hooks";
import { useColors } from "./ColorPrivider";

export default function FormWithCustomHook() {
  const [titleProps, resetTitle] = useInput("");
  const [colorProps, resetColor] = useInput("#000000");
  /** コンテキストからデータを取得 */
  const { addColor } = useColors();

  const submit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    addColor(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>
  );
}
```

### ポイント①: コンテキストをカスタムフックとしてエクスポートする

コード量的には少し減るかなぐらいですが、コンテキストをカスタムフックで**隠蔽**できることがデカイです。
```useState```などのフックと同じ感覚で使用でき、コンテキスト云々を意識する必要がなくなります。

**通常**

```tsx
import { useContext } from "react";
import { ColorContext } from "./index"

export default function App() {
  const colors = useContext(ColorContext)
  // ...
}
```

**カスタムフック**

```tsx
import { useColors } from "./ColorPrivider";

export default function App() {
  const { colors } = useColors()
  // ...
}
```

### ポイント②: ステートの用途を限定して公開する

これは賛否ありそうですが、ある程度の規模以上のAppであれば考える必要があるかなと。。
```setColors```はステート```colors```を更新するための関数なので、公開するとどんな操作も可能になります。
こちらが意図しない操作で、思わぬバグを引き起こす可能性があります。

```tsx
/** 特定の文字列から始まる色を除外する */
const unintentionalAction() => {
  setColors(colors.filter(color => !color.color.startsWith("#ff")));
}
```

<br>

コンテキストは後に出てくる``useReducer``との組み合わせで複雑なステート管理を実現できたりするので、
かなり重要な機能の一つかなと思います。。

## 次回

コンポーネントを描画->ステートを更新->コンポーネントを再描画->...
というサイクルはもうなんとかなりそうですね。。

じゃあコンポーネントが描画された後に何かしたいときはどうすればいいのか。。
（APIを叩くとか、DOMを書き換えるとか、、）

というわけで次回は、、

- **useEffectとuseLayoutEffect**
- **useMemoとuseCallback**

です。なんか盛り沢山ですが色違いみたいなもんです。。

## 参考文献

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