Propのバケツリレー地獄(=Prop Drilling)を防ぐReact Context
Reactでpropに役立つReact Contextについて、便利な点や使い方をまとめ、実装まで行った。
概要
問題点
React Contextを使わず、propにデータを渡して受け取って、渡して受け取って、、、としていると階層が深いと何を渡しているのか分からなくなる。
例えば、下図のように緑色のbackgroundにあるデータ'languages'をcountainer3に渡したい時、React Contextを使う場合と使わない場合の二つのパターンで実装することができる。
パターン1(React Contextを使う場合)
パターン2(React Contextを使わない場合)
パターン2のReact Contextを使わない場合は、Background → Container1 → Container2 → Container3の順にデータが渡っている。データはContainer3でしか使わないにも関わらず、Propをバケツリレーのように渡している。
これをコードにすると以下のような感じ。
import "./App.css";
function App() {
const languages = ["JavaScript", "Python", "Java", "Golang"];
return (
<div className="background">
Background
<p className="languages">
Pass languages[JavaScript, Python, Java, Golang] to Container 3
</p>
{/* languages -> Container1*/}
<Container1 languages={languages} />
</div>
);
}
export default App;
function Container1({ languages }) {
return (
<div className="container1">
Container 1
<Container2 languages={languages} />
{/* Container1 -> Container2 */}
</div>
);
}
function Container2({ languages }) {
return (
<div className="container2">
Container 2
<Container3 languages={languages} />
{/* Container2 -> Container3 */}
</div>
);
}
function Container3({ languages }) {
return (
<div className="container3">
Container 3
<ul className="languages-area">
{languages.map((language, i) => (
<li key={i}>{language}</li>
))}
</ul>
</div>
);
}
Container1やContainer2では、実際にデータを使わないにも関わらず、データを受け取り、Container3までデータを運んでいる。
これだとできないことはないが、コンポーネントが10階層にもなっていた場合、もはや何のデータを渡しているのか、もらっているのか分からなくなる。
解決策
そこでReact Contextを使って、BackgroundからComponent3に直接データを渡す。
使い方は簡単で、React Contextのイメージはこんな感じ。
AさんとCさん共通の友達Bさんをcreateして、Bさんを通じてデータを渡したり受け取ったりするイメージ。
実装
1. react contextをインポート
まず、react contextをインポートする。
その後、createContext()を使って、LanguageContextというcontextを宣言。
↑ここで作ったものがいわゆるAさんとCさんの共通の友達Bさんで、データを受け取ったり、渡したりをしてくれるイメージ。
import "./App.css";
{/* 下記二つ */}
import { createContext, useContext } from "react";
const LanguageContext = createContext();
2. データをcontextに渡す
Provider (=提供する側)を使って、渡したいデータを先ほど作ったLanguageContextに渡す。
共通の友達Bさんにデータを渡すイメージ。
注意点
・LanguageContextに渡したデータを受け取れる範囲は、LanguageContextで囲った中だけ
・LanguageContext.Providerの中に書く値は'value'にしなければならない
import "./App.css";
{/* 下記二つ */}
import { createContext, useContext } from "react";
const LanguageContext = createContext();
{/* ここから下を追加 */}
function App() {
const languages = ["JavaScript", "Python", "Java", "Golang"];
return (
<LanguageContext.Provider value={{ languages }}>
<div className="background">
Background
<p className="languages">
Pass languages[JavaScript, Python, Java, Golang] to Container 3
</p>
<Container1 />
</div>
</LanguageContext.Provider>
);
}
3. データの受け渡しに関係ない部分の記述
propを受け取ったり、渡したりをしなくていい分、コードがスッキリする。
import "./App.css";
{/* 下記二つ */}
import { createContext, useContext } from "react";
const LanguageContext = createContext();
function App() {
const languages = ["JavaScript", "Python", "Java", "Golang"];
return (
<LanguageContext.Provider value={{ languages }}>
<div className="background">
Background
<p className="languages">
Pass languages[JavaScript, Python, Java, Golang] to Container 3
</p>
<Container1 />
</div>
</LanguageContext.Provider>
);
}
{/* ここから下を追加 */}
function Container1() {
return (
<div className="container1">
Container 1
<Container2 />
</div>
);
}
function Container2() {
return (
<div className="container2">
Container 2
<Container3 />
</div>
);
4. データをcontextから受け取る
データを受け取りたいコンポーネントの部分(今回はContainer3)で、useContext()を使って、データを受け取る。
useContext()の中には、context = LanguageContextを入れて,
useContext(LanguageContext)とする。すると、Providerが渡したデータを受け取れる。
import { useContext } from "react";
function Container3() {
const ctx = useContext(LanguageContext);
console.log(ctx.languages);
}
すると、しっかりとデータを受け取ることができている。
["JavaScript", "Python", "Java", "Golang"];
5. 受け取ったデータを使う
ここまで来れば、propで受け取ったのと同じようにデータを使うだけ。
import "./App.css";
{/* 下記二つ */}
import { createContext, useContext } from "react";
const LanguageContext = createContext();
function App() {
const languages = ["JavaScript", "Python", "Java", "Golang"];
return (
<LanguageContext.Provider value={{ languages }}>
<div className="background">
Background
<p className="languages">
Pass languages[JavaScript, Python, Java, Golang] to Container 3
</p>
<Container1 />
</div>
</LanguageContext.Provider>
);
}
function Container1() {
return (
<div className="container1">
Container 1
<Container2 />
</div>
);
}
function Container2() {
return (
<div className="container2">
Container 2
<Container3 />
</div>
);
{/* ここから下を追加 */}
function Container3() {
const ctx = useContext(LanguageContext);
console.log(ctx.languages);
return (
<div className="container3">
Container 3
<ul className="languages-area">
{ctx.languages.map((language, i) => (
<li key={i}>{language}</li>
))}
</ul>
</div>
);
}
補足
本来であれば、storeというフォルダを作成して、その中にLanguageContext.js等のファイルを作る。そしてcontextを作成してエクスポートをする方がいいそうだが、今回は見やすさ重視のため、同じファイルに全て記載している。
import { createContext } from "react";
const LanguageContext = createContext();
export default LanguageContext
まとめ
CSS含め、今回のコードはGitHubにも。
Discussion