実務でReactをいろいろ経験させてもらってから、ようやくReact Hooksの便利さに気づき始めたラスカルです。こんにちは。
今回は、タイトルの通りではあるのですが、useContextとuseStateを組み合わせることで、子孫コンポーネントから、propsのバケツリレーをせずに先祖コンポーネントのstateを更新する実装をTypeScriptで実装したいと思います。
(強調したのは、jsでの実装はあるもののtsでの実装がすぐに見つからなかったからです😩)
##前提条件
import React, { useState } from 'react'
//親コンポーネント
const Parent: React.FC = () => {
const [count, setCount] = useState(0)
return (
<>
<Child />
</>
)
}
//子コンポーネント
const Child: React.FC = () => {
return (
<>
<GrandChild />
</>
)
}
//孫コンポーネント
const GrandChild: React.FC = () => {
return (
<>
<button></button>
<button></button>
</>
)
}
親コンポーネントはuseStateを使って、状態管理をしています。
このcountの値を更新するためには、setCountを使ってあげる必要がありますね。
これを孫コンポーネントでボタンをおすイベントで、行いたいというわけです。
結論
useContextを使い、propsのバケツリレーをしなくても、孫要素から直接親要素のstateを更新できるようにします。
手法としてはuseContextで直接count, setCountを孫コンポーネントに送ってしまいます。
import React, { useState, useContext } from 'react'
//親コンポーネント
//useContextの初期値を設定。
const CountContext = React.createContext({} as {
count: number
setCount: React.Dispatch<React.SetStateAction<number>>
})
const Parent: React.FC = () => {
const [count, setCount] = useState(0)
return (
<>
//孫コンポーネントを含む子コンポーネントをuseContextで定めた変数で囲む。
//valueでcountとsetCountをオブジェクトで渡している点に注意
<CountContext.Provider value={{ count, setCount }}>
<Child />
</CountContext.Provider >
</>
)
}
//子コンポーネント
//特に変更なし
const Child: React.FC = () => {
return (
<>
<GrandChild />
</>
)
}
//孫コンポーネント
const GrandChild: React.FC = () => {
// 親要素で指定した変数を受け取る
const {count, setCount} = useContext(CountContext)
return (
<>
//親要素のuseStateがそのまま使える!
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
</>
)
}
ここで大事なのは親コンポーネントの
const CountContext = React.createContext({} as {
count: number
setCount: React.Dispatch<React.SetStateAction<number>>
})
の部分です。
jsでは
const CountContext = React.createContext()
と、初期値を特に設定しなくてもいいようですが、(いい意味で)型に厳しいTypeScriptでは初期値を必ず設定してあげる必要があるようです。
##補足
もし親コンポーネントと孫コンポーネントが別ファイル(というか、その状況の方が多い気もするけど)の場合、
- 親要素のuseContextで設定した変数にexportをつけてあげる
- 孫要素でその変数をインポートする
必要があります。
【親コンポーネント】
import React, { useState } from 'react'
//useContextの初期値を設定。
export const CountContext = React.createContext({} as {
count: number
setCount: React.Dispatch<React.SetStateAction<number>>
})
const Parent: React.FC = () => {
const [count, setCount] = useState(0)
return (
<>
//孫コンポーネントを含む子コンポーネントをuseContextで定めた変数で囲む。
//valueでcountとsetCountをオブジェクトで渡している点に注意
<CountContext.Provider value={{ count, setCount }}>
<Child />
</CountContext.Provider >
</>
)
}
【子コンポーネント】
//特に変更なし
const Child: React.FC = () => {
return (
<>
<GrandChild />
</>
)
}
【孫コンポーネント】
import { CountContext } from './Parent.tsx'
const GrandChild: React.FC = () => {
// 親要素で指定した変数を受け取る
const {count, setCount} = useContext(CountContext)
return (
<>
//親要素のuseStateがそのまま使える!
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
</>
)
}
最後に
propsのバケツリレーがなくなったので、改修がかなりしやすくなりましたね。
ただuseContextは今回で言う親コンポーネントと孫コンポーネントの依存性をバリバリに強くしてしまうので、再利用性がやや失われてしまいます。
ご利用は計画的に🙏