SAMURAI TERAKOYA

【React hooks】useContextの基礎を習得 – カウンターアプリ

React.js

※ 本記事は、以下の記事の続きになります。

上記の記事では、useReducerを用いて、複雑に処理が分岐するプログラム上でStateを管理するための方法をカウンターアプリを修正しながら確認していきました。
本記事では、useContextを用いて、親から子へのみのデータの共有だけでなく、異なるコンポーネント間でStateを共有できるようにしていきます。

useContextとは

冒頭で少し触れましたが、useContextとは、「異なるコンポーネント間でのStateの共有」を可能にする関数になります。

通常、propsを受け渡す場合は、親コンポーネントから子コンポーネントへの一方通行になるため、子コンポーネントから親コンポーネントへ渡すことは出来ません。また、フォルダの階層が深いと値を受け渡す際にpropsのバケツリレーが発生することがあります。

特にpropsのバケツリレー自体が悪いことではないですが、useContextを使うことで異なる階層であってもデータの共有が出来るようになるため、propsのバケツリレーを最小限に抑えることが出来ます。

また、様々なコンポーネント間で同じ値を使う際にもuseContextは、便利です。

useContext_複数のコンポーネント間で値を共有

上の画像は、Reactが提供しているContextという機能を通してState(データ)の受け渡しが行われている様子を表しています。

ここまで簡単に説明してきましたが、この段階で完璧に理解できなくても問題ありません。実際にコードを書いていきながら流れをつかんでいきましょう。

カウンターアプリの書き換え

フォルダ階層

フォルダの階層は次のようにします。

src 
 - child
   - CounterResult.js
 - context
   - CounterContext.js
App.js
Counter.css

データ(State)を収容するためのコードを記述

まずは、contextフォルダ内にあるCounterContext.jsに対し、Stateを収容(管理)するためのコードとReactが提供しているContext機能を用いていく上でのコードを記述していきます。

CounterContext.js
  • 必要な関数を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オブジェクトを設定します。

    useContext用の関数を定義する際には、関数名の先頭にuseを付けるようにしてください。そうしないと、他のコンポーネント上でimportしても正常に動作しません。

  • ProviderやuseContext用の関数をexportする

    最後にProviderとuseContext用の関数をexportしましょう。

    export { Provider, useCountVal, useDispatch }

※ 最終的なコードについては、こちらからご確認ください。

 

CounterResult.jsの修正

続いて、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へ移動させていますので、一部のみ変更していきます。

App.js
  • 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でカウンターアプリを作成していこうと思います。

 

コメント

タイトルとURLをコピーしました