※ 本記事は、以下の記事の続きになります。
useContextとは
冒頭で少し触れましたが、useContextとは、「異なるコンポーネント間でのStateの共有」を可能にする関数になります。
通常、propsを受け渡す場合は、親コンポーネントから子コンポーネントへの一方通行になるため、子コンポーネントから親コンポーネントへ渡すことは出来ません。また、フォルダの階層が深いと値を受け渡す際にpropsのバケツリレーが発生することがあります。
特にpropsのバケツリレー自体が悪いことではないですが、useContextを使うことで異なる階層であってもデータの共有が出来るようになるため、propsのバケツリレーを最小限に抑えることが出来ます。
また、様々なコンポーネント間で同じ値を使う際にもuseContextは、便利です。
上の画像は、Reactが提供しているContextという機能を通してState(データ)の受け渡しが行われている様子を表しています。
ここまで簡単に説明してきましたが、この段階で完璧に理解できなくても問題ありません。実際にコードを書いていきながら流れをつかんでいきましょう。
カウンターアプリの書き換え
フォルダ階層
フォルダの階層は次のようにします。
src
- child
- CounterResult.js
- context
- CounterContext.js
App.js
Counter.css
データ(State)を収容するためのコードを記述
まずは、contextフォルダ内にあるCounterContext.jsに対し、Stateを収容(管理)するためのコードとReactが提供しているContext機能を用いていく上でのコードを記述していきます。
- ①必要な関数をimportする
createContextは、Contextオブジェクトの作成をするために使用し、useContextで作成されたContextを他のコンポーネントで使えるようにします。
useReducerをimportしている理由としては、他のコンポーネントと共有できるようにするためには、Stateの状態を管理するコードも記述する必要があるためです。
import { createContext, useContext, useReducer } from "react";
- ②Contextオブジェクトの作成
Counterでは、現在のState, CounterDispatchでは、更新する関数を保持させていきます。
const Counter = createContext(); const CounterDispatch = createContext();
- ③Providerコンポーネントを記述する
Providerコンポーネントは、作成したContextオブジェクト内に入っており、Contextをコンポーネント上で使えるようにするために必要なものになります。
App.js内のuseReducerに関する記述をProviderコンポーネント内に移動させます。残したままですと、エラーの原因になるため、App.js上からは削除するかコメントにするようにしてください。
const Provider = ({ children }) => { const [countval, dispatch] = useReducer((prevcount, { value }) => { switch (value) { case "add": return prevcount + 1; case "minus": return prevcount > 0 ? prevcount - 1 : prevcount; case "reset": return initialState; default: throw new Error('エラーです。') } }, 0); return ( <Counter.Provider value={countval}> <CounterDispatch.Provider value={dispatch}> {children} </CounterDispatch.Provider> </Counter.Provider> ) }
returnより以下は、作成したContextをコンポーネント上で使えるようにするためのコードを記述しています。
<Contextオブジェクト.Provider value={渡す値}>~</Contextオブジェクト.Provider>valueには、他のコンポーネントへ渡す値を指定します。今回ですと、countvalなので現在の数値を渡すことになります。
- ④useContextを記述する
ここまででProviderも設定し、他のコンポーネントと値を共有するための準備をしてきました。しかし、このままだと正常に動作しませんので、実際に値を共有する上で橋渡しの役割をもつuseContextを記述していきましょう。
const useCountVal = () => { return useContext(Counter); } const useDispatch = () => { return useContext(CounterDispatch); }
2つのContextオブジェクトを作成してますので、2つ分関数を準備します。戻り値としてはuseContextを記述し、引数としてContextオブジェクトを設定します。
- ⑤ProviderやuseContext用の関数をexportする
最後にProviderとuseContext用の関数をexportしましょう。
export { Provider, useCountVal, useDispatch }
※ 最終的なコードについては、こちらからご確認ください。
CounterResult.jsの修正
続いて、CounterResult.jsのコード修正を行っていきます。
- ①useContext用の関数をimportする
関数ですので、{}で囲みます。
import { useCountVal, useDispatch } from "../context/CounterContext";
- ②useContext用の関数を設定
useContext用の関数を設定することで、CounterResult.js上でdispatch関数と現在のStateを扱えるようになります。この作業が抜けると、Contextを使うことが出来ないため気を付けましょう。
※ ②以降は、CounterResultコンポーネント内に記述します。
const countval = useCountVal(); const dispatch = useDispatch();
- ③ボタンクリック時に呼ばれる関数をApp.jsから移動させる
dispatchに関するuseContextをこのファイル内で設定してますので、App.js内に記述されている各関数をCounterResult.jsへ移動させます。
const CounterUp = () => { dispatch({value: "add"}); } const CounterDown = () => { dispatch({value: "minus"}); } const CounterReset = () => { dispatch({value: "reset"}); }
- ④画面に出力する部分を記述する
ここに関しては、基本的にコードの修正はありません。変わった点があるとすればApp.jsからpropsとして受け取るのではなく、Contextからデータを受け取っているところになります。
return ( <> <h2>{countval}</h2> <div className="button_group"> <button onClick={CounterUp}>+</button> <button onClick={CounterDown}>-</button> <button onClick={CounterReset}>reset</button> </div> </> );
※ 最終的なコードについては、こちらからご確認ください。
App.jsの修正
最後にApp.jsの修正を行います。現時点で既に殆どのコードをCounterResultへ移動させていますので、一部のみ変更していきます。
- ①Provider及び各種ファイル等をimportする
import CounterResult from "./child/CounterResult" import { Provider } from "./context/Context"; import "./Counter.css"
- ②Providerでラップする
Providerでラップした(囲んだ)コンポーネントは、同一の Contextオブジェクトを参照できるようになります。つまり、以下のコードだとProviderでラップされているCounterResultは、Contextオブジェクトを参照できるようになりますが、App.js上では参照出来ないということになります。
App.js上でも参照できるようにするためには、index.js上でProviderをimportし、AppをProviderで囲んであげる必要があります。
return ( <> <div className="main"> <h1>Counter_App</h1> <Provider> <CounterResult /> </Provider> </div> </> );
※ 最終的なコードについては、こちらからご確認ください。
Counter.cssの修正
修正箇所については、useReducerの時と同様に色に関する部分のみになります。
#root {
width: 100%;
height: 300px;
display: flex;
justify-content: center;
align-items: center;
background-color: rgb(252, 244, 220); /* 修正箇所 */
}
.main {
margin: 0 auto;
border: 2px dashed rgb(255, 174, 0); /* 修正箇所 */
padding: 30px;
}
.main h1 {
color: rgb(100, 59, 12); /* 修正箇所 */
}
※ 最終的なコードについては、こちらからご確認ください。
動作確認
ここまで、お疲れ様でした。難しい箇所もあったかと思いますが、一通りコードの入力が出来ましたら、動作の確認をしてみましょう。
cd プロジェクトフォルダ
npm start
エラーもなく、正常に実行されますと、以下のように画面に表示されます。値が変化することも確認してみてください。
まとめ
本記事では、以下の内容を確認していきました。
- useContextで何が出来るのか
- useContextの使い方
- カウンターアプリの修正
次回は、React + TypeScriptでカウンターアプリを作成していこうと思います。
【React hooks】useState・propsの基礎を習得(カウンターアプリ)
【React hooks】useReducerの基礎を習得(カウンターアプリ)
コメント