プログラミング

Reactでグローバルstateを扱うためのuseContextを紹介

reactのusecontextを解説

今回はReactの機能の1つである、useContextについての解説を行います。

このuseContextを利用することで、様々なコンポーネントから参照できるstateを作ることができます。

つまり、無駄なpropsのバケツリレーなどをしなくて良くなるということです。

グローバルstateは他にもReduxやRecoilなどがありますが、このuseContextが基礎となりますので、しっかり身につけましょう。

今回は、前回のこの記事の続きから実装していきます。

Reactのルーティングについて解説【useHistoryの使い方】Reactのルーティングについて理解してますか?React routerを使えば簡単にルーティングを実装できます。なので今回は、Reactのルーティングについて解説します。useHistoryの使い方も合わせて解説しているので、参考になるかと。...

0からReactエンジニアになるための手順は、全てここで解説しているので合わせてどうぞ。

Reactロードマップ
【完全無料ロードマップ】Reactエンジニアへなるための方法まとめ【現役Reactエンジニアが解説】Reactを学びたいと思ってますか?Reactエンジニアから勉強方法を学ぶのが一番効率的です。なので今回は、現役のReactエンジニアがReactの学習ロードマップを無料で公開します。Reactは今とても需要が高いスキルなので、今すぐ学びましょう。...

Reactでの基本的なグローバルStateの設定

まずは、「グローバルstate」の作り方と、その参照に仕方を説明していきます。

今回ユーザーの情報を「グローバルstate」として設定するので、「UserProvider」というファイルを作成します。

次に、「userInfo」という「state」を宣言します。

中身は、「userName」を設定しておきます。

そして、「createContext」というものを使って、「グローバルstate」を設定していきます。

次に、「UserProvider」というコンポーネントを作成し、「props」の「children」を表示させるようにします。

ここは、もう書き方を覚えてしまうのが一番良いので、コードはこんな感じになります。

import { createContext, useState } from "react";

export const UserContext = createContext({});

export const UserProvider = (props) => {
  const [userInfo, setUserInfo] = useState({userName: "shinya"});
  return(
    <UserContext.Provider value={{userInfo, setUserInfo}}>
      {props.children}
    </UserContext.Provider>
  )
}

これで、「userInfo」という「グローバルstate」を設定することができました。

次に、Home側でそれを参照できるようにします。

まず、どこからでも「グローバルstate」を呼び出せるように、先ほど作成した「UserProvider」で全てのコンポーネントを囲みます。

import './App.css';
import {BrowserRouter, Link} from "react-router-dom";
import {Routes} from "./components/routing/Routes"
import { UserProvider } from './providers/UserProvider';

function App() {
  return (
    <UserProvider>
      <BrowserRouter>
        <div className="App">
          <Link to="/">Home</Link>
          <br />
          <Link to="/page1">Page1</Link>
          <br />
          <Link to="/page2">Page2</Link>
          <br />
          <Link to="/countUp">CountUp</Link>
        </div>
        <Routes />
      </BrowserRouter>
    </UserProvider>
  );
}

export default App;

参照する側は、「useContext」というものを使います。

引数に、先ほど作成した「UserContext」を設定すればOKです。

後は普通にこのように書けば先ほど設定した、「userName」が表示されるかと思います。

import {useContext} from "react";
import {UserContext} from "../../providers/UserProvider";

export const Home = () => {
  const {userInfo} = useContext(UserContext);
  return(
    <>
      <h2>Home</h2>
      <p>{userInfo.userName}</p>
    </>
  );
}

グローバルstateの変更

では次に、「グローバルstate」を変更させ、全てのコンポーネントでその変更が反映されているかを確認します。

今回は、「Page1」で「useInfo」を変更させます。

まずは、変更用にinputエリアとボタンを用意します。
ここに設定した値が新しい「userName」として、変更させるように関数を設定します。

コードはこんな感じです。

import {Link, useHistory} from "react-router-dom";
import {useContext, useState} from "react";
import {UserContext} from "../../providers/UserProvider";

export const Page1 = () => {
  const stateA = "state";
  const history = useHistory();
  const [inputUserName, setInputUserName] = useState("");
  const {userInfo, setUserInfo} = useContext(UserContext)
  const onClickButton = () => {
    //history.push("/page1/detailA")
    history.push({
      pathname: "/page1/detailA",
      state: stateA
    })
  }
  const onChangeInput = (e) => {
    setInputUserName(e.target.value);
  }
  const changeUserName = () => {
    if(inputUserName === ""){
      return
    }
    setUserInfo({userName: inputUserName});
    setInputUserName("");
  }
  return(
    <>
      <Link to={{pathname: "/page1/detailA", state: stateA}}>DetailA</Link>
      <br />
      <Link to="/page1/detailB">DetailB</Link>
      <h2>Page1</h2>
      <br />
      <button onClick={onClickButton}>Jump to DetailA</button>
      <br />
      <p>{userInfo.userName}</p>
      <input type="text" value={inputUserName} onChange={onChangeInput}/>
      <button onClick={changeUserName}>change user name</button>
    </>
  );
}

これで、インプットを指定してボタンを押すと「userName」が変更になりましたが、本当に他のコンポーネントにも反映されているのでしょうか?

Homeを確認すると分かりますが、ちゃんと他のページにも反映されています。

Reactでグローバルstateを使う際の注意点

最後に、「グローバルstate」を使う際の注意点を書いていきます。

「グローバルstate」が変更になると、その「state」を使っている全てのコンポーネントの再レンダリングが走ります。

そこまでは良いのですが、stateを参照していない無関係の子コンポーネントまで変更されることになってしまいます。

なぜなら、Reactの再レンダリングの条件は
・stateが変更になった時
・propsが変更になった時
・親コンポーネントが再レンダリングされた時
なので、最後の条件に当てはまってます。

試しに、Page1コンポーネントの子コンポーネントを作り、そこにログを仕込みます。

import {Link, useHistory} from "react-router-dom";
import {useContext, useState, memo} from "react";
import {UserContext} from "../../providers/UserProvider";

const Page1Child = () => {
  console.log("child")
  return(
    <h2>Page1Child</h2>
  )
};

export const Page1 = () => {
  const stateA = "state";
  const history = useHistory();
  const [inputUserName, setInputUserName] = useState("");
  const {userInfo, setUserInfo} = useContext(UserContext)
  const onClickButton = () => {
    //history.push("/page1/detailA")
    history.push({
      pathname: "/page1/detailA",
      state: stateA
    })
  }
  const onChangeInput = (e) => {
    setInputUserName(e.target.value);
  }
  const changeUserName = () => {
    if(inputUserName === ""){
      return
    }
    setUserInfo({userName: inputUserName});
    setInputUserName("");
  }
  return(
    <>
      <Link to={{pathname: "/page1/detailA", state: stateA}}>DetailA</Link>
      <br />
      <Link to="/page1/detailB">DetailB</Link>
      <h2>Page1</h2>
      <br />
      <button onClick={onClickButton}>Jump to DetailA</button>
      <br />
      <p>{userInfo.userName}</p>
      <input type="text" value={inputUserName} onChange={onChangeInput}/>
      <button onClick={changeUserName}>change user name</button>
      <Page1Child />
    </>
  );
}

グローバルstateが変更される度にその子コンポーネントも再レンダリングされていることがわかるかと思います。

つまり、きちんと再レンダリングの最適化をしていないと無駄な再レンダリングを走らせることになります。

対策は簡単で、単にmemo化してあげれば良いだけです。
コードはこうなります。

import {Link, useHistory} from "react-router-dom";
import {useContext, useState, memo} from "react";
import {UserContext} from "../../providers/UserProvider";

const Page1Child = memo(() => {
  console.log("child")
  return(
    <h2>Page1Child</h2>
  )
});

export const Page1 = () => {
  const stateA = "state";
  const history = useHistory();
  const [inputUserName, setInputUserName] = useState("");
  const {userInfo, setUserInfo} = useContext(UserContext)
  const onClickButton = () => {
    //history.push("/page1/detailA")
    history.push({
      pathname: "/page1/detailA",
      state: stateA
    })
  }
  const onChangeInput = (e) => {
    setInputUserName(e.target.value);
  }
  const changeUserName = () => {
    if(inputUserName === ""){
      return
    }
    setUserInfo({userName: inputUserName});
    setInputUserName("");
  }
  return(
    <>
      <Link to={{pathname: "/page1/detailA", state: stateA}}>DetailA</Link>
      <br />
      <Link to="/page1/detailB">DetailB</Link>
      <h2>Page1</h2>
      <br />
      <button onClick={onClickButton}>Jump to DetailA</button>
      <br />
      <p>{userInfo.userName}</p>
      <input type="text" value={inputUserName} onChange={onChangeInput}/>
      <button onClick={changeUserName}>change user name</button>
      <Page1Child />
    </>
  );
}

これで無駄な再レンダリングが走らなくなったかと思います。

Reactでグローバルstateを扱うためのuseContextを紹介 まとめ

このようにすることで、「グローバルstate」を扱うことができ、無駄なpropsのバケツリレーを無くすことができます。

Reactにおいて、状態管理はとても大事な概念なので、今回学んだことはしっかりと扱えるように復習しておきましょう。

僕のブログでは他にもプログラミングについての記事を書いているので、良かったら見てみてください。

Reactエンジニアになりたい人はこちらからどうぞ

Reactロードマップ
【完全無料ロードマップ】Reactエンジニアへなるための方法まとめ【現役Reactエンジニアが解説】Reactを学びたいと思ってますか?Reactエンジニアから勉強方法を学ぶのが一番効率的です。なので今回は、現役のReactエンジニアがReactの学習ロードマップを無料で公開します。Reactは今とても需要が高いスキルなので、今すぐ学びましょう。...

 

おわり

ABOUT ME
ひのしん
【執筆者情報】ひのしんです。「正しい努力をすれば誰でも成果は出せる」 をモットーに発信しています。元ニートの現フリーランスエンジニアです。 プログラミング歴は4年ほどで、TOEIC885点を取得。科学と実体験を元にした効率的で正しいスキルアップ(プログラミング・英語・ブログ)の方法を発信してます。
\\こちらの記事が大好評です//