はじめに
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>
)
}
おわりに
グローバルの状態管理を必要最低限にまとめました!
これさえ完璧に理解してしまえばあとはいくらでもグローバルで状態を扱えるようになります!