
ReactとTypeScriptでuseContextとuseReducerを使う方法
こんにちわ!
本日の記事では、useContextとuseReducerを使った実装方法を書いてみたいと思います。
useContextというのは、Reactのコンポーネントが複数回の階層に分かれてたときに、
親コンポーネントで定義された値を、孫コンポーネントで使うためには、基本的にはPropで渡していかないといけないですよね。
ですから、親コンポーネントで定義した値は、わざわざ子コンポーネントに渡して、それで子コンポーネントから孫コンポーネントに渡さないといけないわけです。
ですから、親コンポーネントで定義した値を、子コンポーネントの階層を飛ばして孫コンポーネントに渡すことはできません。
でも、これをuseContextを使うと出来るようになります。
それで、useReducerというのは、useStateの複雑化バージョンですので、Stateの記憶装置として使えます。
useContextでコンポーネント間で渡す値が、複雑になったときに、useStateでは処理できない要素を、useReducerを使って処理しましょうというわけです。
今回の記事では、下記の記事を参考に書かせていただきますので、自分なりの解釈を解説しながら、やっていきますので、
興味のある人は、最後までお付き合いください。
useStateを使う
では、まずはuseStateを使ってコードを書いてみましょう。
import React, {useState} from 'react'
const Parent: React.CF = () => {
const [const, setCount] = useState(0)
return (
<div>
<h1>カウント : {count}</h1>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
)
}
export default Parent
こんな感じですね。
シンプルに、カウントを足し引きするだけです。
これを、コンポーネントに分けて、
子コンポーネントと、孫コンポーネントを作ってみましょう。
import React, { useState } from 'react'
const Parent: React.FC = () => {
const [count, setCount] = useState(0)
return (
<div>
<h1>カウント: { count }</h1>
<Child count={count} setCount={setCount} />
</div>
)
}
type Props = {
count: number
setCount: React.Dispatch<React.SetStateAction<number>>
}
const Child: React.FC<Props> = ({count, setCount}) => {
return (
<>
<p>子コンポーネント</p>
<Grandchild count={count} setCount={setCount} />
</>
)
}
const Grandchild: React.FC<Props> = ({count, setCount}) => {
return (
<>
<p>孫コンポーネント</p>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
</>
)
}
こんな感じですね。
これだと、子コンポーネントではcountを使用しないのですが、
Propsを通して値をわざわざ渡さなければならず大変面倒くさいことになってしまいます。
これを、useContextを使うことで、stateを孫のコンポーネントから直接参照することが出来るようになります。
useContextを使う
import React, { useState, createContext, useContext } from 'react'
const CountContext = createContext({} as {
count: number
setCount: React.Dispatch<React.SetStateAction<number>>
})
const Parent: React.FC = () => {
const [count, setCount] = useState(0)
return (
<CountContext.Provider value={{ count, setCount }}>
<h1>カウント: { count }</h1>
<Child />
</CountContext.Provider>
)
}
const Child: React.FC = () => {
return (
<>
<p>子コンポーネント</p>
<Grandchild />
</>
)
}
const Grandchild: React.FC = () => {
const { count, setCount } = useContext(CountContext)
return (
<>
<p>孫コンポーネント</p>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
</>
)
}
このコードを解説しますと、
<CountContext.Provider value={{ count, setCount }}>
<h1>カウント: { count }</h1>
<Child />
</CountContext.Provider>
このように、CountContext.Providerで囲った下の階層のコンポーネントでは、全てのコンポーネントで、コンテクストにアクセスできます。
const Grandchild: React.FC = () => {
const { count, setCount } = useContext(CountContext)
このように、孫のコンポーネントで、親コンポーネントで定義したcountとsetCountを操作できるようになります。
useReducerを使う
useContextを使うことで、子コンポーネントでは、親コンポーネントで定義したstateを使わないで済むようになりました。
ここで、少しuseReducerを使うことを考えてみたいんですけれども、
今、孫コンポーネントでボタンを使ってるのは、countを上げ下げして、それをsetCountするだけだと思います。
しかし、もっと複雑な条件下で、ステートの管理をするようになると、面倒くさいことになってきますよね。
Contextを増やさないといけないかもしれません。
ですので、もし管理するステートが増えても大丈夫なように、useReducerを書いてみましょう。
import React, { useReducer } from 'react'
type State = {
count: number
};
type Action =
{ type: 'INCREMENT' } |
{ type: 'DECREMENT' }
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
}
}
const initialState = {
count: 0
}
const Parent: React.FC = () => {
const [state, dispatch] = useReducer(reducer, initialState)
return (
<div>
<h1>カウント: { state.count }</h1>
<button onClick={() => dispatch({type:'INCREMENT'})}>+</button>
<button onClick={() => dispatch({type:'DECREMENT'})}>-</button>
</div>
)
}
export default Parent
useReducerを使うには、dispatchを使うのが特徴ですね。
では、これを踏まえたうえで、
ステート更新の条件が複雑になった上で、コンポーネント間で値を渡さなくても使えるように、useContextと、useReducerを使ってみましょう。
useContext/useReducerを使う
import React, { useReducer, createContext, useContext } from 'react'
type State = {
count: number
}
type Action =
{ type: 'INCREMENT' } |
{ type: 'DECREMENT' }
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
}
}
const AppContext = createContext({} as {
state: State
dispatch: React.Dispatch<Action>
})
const initialState = {
count: 0
}
const Parent: React.FC = () => {
const [state, dispatch] = useReducer(reducer, initialState)
return (
<AppContext.Provider value={{ state, dispatch }}>
<h1>カウント: { state.count }</h1>
<Child />
</AppContext.Provider>
)
}
const Child: React.FC = () => {
return (
<>
<p>子コンポーネント</p>
<Grandchild />
</>
)
}
const Grandchild: React.FC = () => {
const { dispatch } = useContext(AppContext)
return (
<>
<p>孫コンポーネント</p>
<button onClick={() => dispatch({type:'INCREMENT'})}>+</button>
<button onClick={() => dispatch({type:'DECREMENT'})}>-</button>
</>
)
}
こんな感じですね。
このコードを解説しますと、
const [state, dispatch] = useReducer(reducer, initialState)
return (
<AppContext.Provider value={{ state, dispatch }}>
<h1>カウント: { state.count }</h1>
<Child />
</AppContext.Provider>
まず、ここで、カウント : {state.count}を、孫コンポーネントで操作したいわけですよね。
useContextを使ってuseReducerを下の階層で使えるようにしてるんで、
const Grandchild: React.FC = () => {
const { dispatch } = useContext(AppContext)
return (
<>
<p>孫コンポーネント</p>
<button onClick={() => dispatch({type:'INCREMENT'})}>+</button>
<button onClick={() => dispatch({type:'DECREMENT'})}>-</button>
こういうことが可能というわけです。
以上になります。