【React状態管理2】useContext

# 概要
Reactの状態管理の1つである、`useContext`について学んだことをメモします。

# 学習内容
- [useContextとは](#useContextとは)
- [useContextを複数使う](#usecontextを複数使う)
- [useContextの問題点](#usecontextの問題点)

# useContextとは
`useContext`とは、Reactで提供されているコンポーネントツリー内でのデータ共有機能。
`props`によるバケツリレーを行うことなく、コンポーネント間でデータを共有できる。

### 利用手順
1. Context領域の生成
`createContext()`により、Context領域を生成する。通常、最上位のページ又はコンポーネントで用いる（_app.tsx等）。
  ```javascript
  export const TodoContext = createContext(TODOS);
  ```

2. Context領域の利用範囲の設定
生成したContext領域により、子ページまたは子コンポーネントをラップする（ラップされたコンポーネント内で、Context領域のデータが使える）。
ラップする際には、Context領域の`Provider`コンポーネントを呼び出す。
Context領域に設定するデータの値は、`Provider`コンポーネントの引数`value`に保存する
  ```javascript
  return (
    <TodoContext.Provider value={todos}>
      <Component {...pageProps} todos={todos} setTodos={setTodos} />
    </TodoContext.Provider>
  );
  ```

3. Context領域のデータの利用
Context領域に保存したデータを使用したいページやコンポーネントで、`useContext()`を呼び出す
  ```javascript
  const todos = useContext(TodoContext);
  return (
      <h2>TODO: {todos.length}件</h2>
  )
  ```

### 使用例
1. 変数`todos`を、Context領域`TodoContext`に保存<br>
1. Context領域の値`todos`を、`props`を用いることなく別コンポーネントTodoCounter.tsxで利用

```javascript: _app.tsx
import { createContext, useState } from "react";

const TODOS: Todo[] = [
  { id: 1, text: "foo", isDone: false },
  { id: 2, text: "bar", isDone: true },
];

export const TodoContext = createContext(TODOS); // Context領域の生成

export default function MyApp({ Component, pageProps }: AppProps) {
  const [todos, setTodos] = useState(TODOS);
  
  return (
    <TodoContext.Provider value={todos}>            {/*Context領域による子コンポーネントのラップ*/}
      <Component {...pageProps} todos={todos} setTodos={setTodos} />
    </TodoContext.Provider>                         {/*Context領域による子コンポーネントのラップ*/}
  );
}
```

```javascript: TodoCounter.tsx
import { FC, useContext } from "react";
import { TodoContext } from "src/pages/_app";

export const TodoCounter: FC = () => {
    const todos = useContext(TodoContext); // propsを用いずにContext領域からTodoContext内の値todosを取得
    return (
        <h2>TODO: {todos.length}件</h2>
    )
}
```

# useContextを複数使う
`useContext`は同一ページ内で複数使うことができる。

```javascript:_app.tsx
export const ThemeContext = createContext("light");
export const LangContext = createContext("ja");

export default function MyApp({ Component, pageProps }: AppProps) {
  const [todos, setTodos] = useState<Todo[]>(TODOS);
  const [theme, setTheme] = useState("light");
  const [lang, setLang] = useState("ja");

  return (
    <ThemeContext.Provider value={theme}>
      <LangContext.Provider value={lang}>
        <Layout todoCount={todos.length}>
          <button 
            onClick={() => {
              setTheme((prev) => prev === "dark" ? "light" : "dark");
              setLang((prev) => prev === "ja" ? "en" : "ja");
            }}
          >
            テーマ切り替え
          </button>
          <Component {...pageProps} todos={todos} setTodos={setTodos} />
        </Layout>
      </LangContext.Provider>
    </ThemeContext.Provider>
  );
}
```

# useContextの問題点
`useContext`にはいくつか問題点があるため、外部パッケージ(Redux, Recoilなど)の状態管理を使うことが多い
- 記述量が多い
- `useCallback()`など、Reactの関数を使う必要がある
- 複数の状態を持たせると状態同士の連携が困難
- SSRとの相性があまりよくない



# 参考
[IT Kingdom](https://it-kingdom.com)
[GitHubリポジトリ](https://github.com/yamashin01/react_state_management/tree/main/Context03)
[Context](https://ja.reactjs.org/docs/context.html)