今回はReactの機能の1つである、useContextについての解説を行います。
このuseContextを利用することで、様々なコンポーネントから参照できるstateを作ることができます。
つまり、無駄なpropsのバケツリレーなどをしなくて良くなるということです。
グローバルstateは他にもReduxやRecoilなどがありますが、このuseContextが基礎となりますので、しっかり身につけましょう。
今回は、前回のこの記事の続きから実装していきます。
Reactエンジニアを目指している方へ、
知識0から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のおすすめ教材を知りたい人はこの記事をどうぞ。
「プログラミングスクールが高くて通えない。。」
といった悩みがこのサービスで解決します。
それは、次世代型のサブスクプログラミングスクールになります。
具体的に、このスクールは以下のことが可能です。
- 講師とのマンツーマンレッスン
- 質の高いかなりボリュームのある教材
- 講師に質問し放題
そして、お値段はたったの1,980円から。
これで高いお金を払わずに、エンジニアになることが可能です。
今なら、全額返金保証もあります。
エンジニアを目指す人も年々増えているで、お早めにどうぞ。
当サイト限定の、初月50%OFFクーポン(HINOSHIN)あり
>>侍テラコヤの評判・口コミ|現役エンジニアが実際に使ってみた感想