React(Next.js)におけるuseContext, useReducerを用いたグローバル状態管理

# はじめに
最終的な成果物は↓のようになります
![Reactでのグローバル状態管理.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/748651/a03adaa1-2188-0744-3a51-bddbf56bcd60.gif)



# 1. State, Actionの型定義
- State：参照したい状態を定義。今回はcountという変数をグローバルで持たせます
- Action：状態をどういう風に状態を更新するかを定義。今回はプラスボタンでカウントを増やすINCREMENT、マイナスボタンでカウントを減らすDECREMENTを定義

```ts:lib/interfaces.ts
export type State = {
  count: number
}
export type Action = { type: 'INCREMENT' } | { type: 'DECREMENT' }
```

# 2. 状態の保管庫であるContextの定義
- createContextで実際の状態の保管庫を作成しますこのContext経由で状態の参照、状態の更新を行います
- これは決まり文句的な感じです。ただ、上記で定義した型を用いて型注釈することで、入力補完、型チェックの恩恵を受けることができます

```ts:lib/store/context.ts
import { createContext, Dispatch } from 'react'
import { State, Action } from 'lib/interfaces'

const Context = createContext<{
  state: State
  dispatch: Dispatch<Action>
}>(null)
export default Context
```

# 3. actionに応じて状態を更新するreducerを定義
- reducerには受け取ったactionに応じた処理をswitch文で分岐させて記述します。actionのtypeに応じてカウントを１増やす、１減らすという状態の更新処理を行います

```ts:lib/store/reducer.ts
import { State, Action } from 'lib/interfaces'

export const reducer = (state: State, action: Action) => {
  switch (action.type) {
    case 'INCREMENT':
      return {
        ...state,
        count: state.count + 1,
      }
    case 'DECREMENT':
      return {
        ...state,
        count: state.count - 1,
      }
    default:
      return state
  }
}
```

# 4. グローバルで状態を参照、更新するために最上位のコンポーネントをProviderでラップ
- 状態を保持している変数state、状態を更新する関数dispatchをContext.Providerのvalueにセットすることで、その配下のコンポーネントで状態の参照、更新ができるようになります

```tsx:pages/_app.tsx
import '../styles/globals.scss'

import Context from 'lib/store/context'
import { reducer } from 'lib/store/reducer'
import { useReducer } from 'react'

export default function MyApp({ Component, pageProps }) {
  const initialState = { count: 0 }
  const [state, dispatch] = useReducer(reducer, initialState)
  return (
    <Context.Provider value={{ state, dispatch }}>
      <Component {...pageProps} />
    </Context.Provider>
  )
}
```
# 5. 状態を更新するプラスボタン、マイナスボタンコンポーネントを作成
- dispatch関数にactionのtypeであるINCREMENT、DECREMENTを渡すことで、上記で作成したreducerが対応した処理を行ってくれます

```tsx:components/ButtonIncrement.tsx
import { useContext } from 'react'
import Context from 'lib/store/context'

export default function ButtonIncrement() {
  const { dispatch } = useContext(Context)

  return <button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
}
```
```tsx:components/ButtonDecrement.tsx
import { useContext } from 'react'
import Context from 'lib/store/context'

export default function ButtonDecrement() {
  const { dispatch } = useContext(Context)

  return <button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
}
```

# 6. 状態を参照するコンポーネントを作成
- useContextに作成したContextを渡すことで状態を参照できるようになります
- ButtonIncrement, ButtonDecrementコンポーネントが押されると状態が更新されます。そうすると自動的に更新を検知し、コンポーネントの垣根をこえてstateの中身もその更新内容が反映されます。

```tsx:pages/index.tsx
import { useContext } from 'react'
import Context from 'lib/store/context'
import ButtonIncrement from 'components/ButtonIncrement'
import ButtonDecrement from 'components/ButtonDecrement'

export default function Index() {
  const { state } = useContext(Context)
  return (
    <div>
      <h1>{state.count}</h1>
      <ButtonIncrement />
      <ButtonDecrement />
    </div>
  )
}
```

# おわりに
- 最終的なフォルダ構成は↓のようになっていると思います！
![スクリーンショット 2021-09-04 2.37.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/748651/2208d323-718e-a67b-5f8d-d0b1123dd636.png)

グローバルの状態管理を必要最低限にまとめました！
これさえ完璧に理解してしまえばあとはいくらでもグローバルで状態を扱えるようになります！