8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Posted at

はじめに

最終的な成果物は↓のようになります
Reactでのグローバル状態管理.gif

1. State, Actionの型定義

  • State:参照したい状態を定義。今回はcountという変数をグローバルで持たせます
  • Action:状態をどういう風に状態を更新するかを定義。今回はプラスボタンでカウントを増やすINCREMENT、マイナスボタンでカウントを減らすDECREMENTを定義
lib/interfaces.ts
export type State = {
  count: number
}
export type Action = { type: 'INCREMENT' } | { type: 'DECREMENT' }

2. 状態の保管庫であるContextの定義

  • createContextで実際の状態の保管庫を作成しますこのContext経由で状態の参照、状態の更新を行います
  • これは決まり文句的な感じです。ただ、上記で定義した型を用いて型注釈することで、入力補完、型チェックの恩恵を受けることができます
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に応じてカウントを1増やす、1減らすという状態の更新処理を行います
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にセットすることで、その配下のコンポーネントで状態の参照、更新ができるようになります
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が対応した処理を行ってくれます
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>
}
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の中身もその更新内容が反映されます。
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

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

8
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?