こんにちは!Reactの学習、毎日お疲れ様です。
画面に文字やボタンを表示できるようになって、「よし、自分のアプリを作るぞ!」と意気込んだ矢先、なんだかコードが複雑になって手が止まってしまう……そんな時期って、本当に誰にでもあります。私も昔は、コンポーネントが何重にも深くなると頭から煙が出ていました。
今日お話しするのは、そんなReact初学者の誰もが一度はぶつかる壁、「Prop Drilling(プロップドリリング)」と、それをスマートに解決する「Context APIの型定義」についてです。
難しそうな名前が並んでいますが、大丈夫です!身近な例えを交えながら、一つひとつ優しくほどいていきますね。
—
1. 「Prop Drilling」ってなんだろう?(おつかいリレーの悲劇)
Reactでは、親から子へ、子から孫へと、`props`(プロップス)という荷物を使ってデータをリレー形式で渡していきます。
例えば、こんな状況を想像してみてください。
あなたは家族5人で暮らしていて、一番下の末っ子(孫コンポーネント)が「お小遣いが欲しい!」と言っています。お財布を持っているのはお父さん(親コンポーネント)です。
- お父さん(お金を持っている)
↓ 「これ、お母さんに渡して」
- お母さん(中継ぎ)
↓ 「これ、お兄ちゃんに渡して」
- お兄ちゃん(中継ぎ)
↓ 「はい、末っ子どうぞ」
- 末っ子(お金を受け取る)
お母さんもお兄ちゃんも、本当はそのお金には何の用事もないのに、「末っ子に届けるためだけ」に、わざわざ受け取って次の人に渡す作業をさせられています。
これがプログラムの世界での「Prop Drilling(データの孫引き地獄・バケツリレー)」です。
コンポーネントの階層が深くなればなるほど、途中のコンポーネントは「使わないのに、下の階層に渡すために名前を書き続けなきゃいけない」という面倒な罰ゲーム状態になってしまいます。
「もっとこう、みんながアクセスできる『家族の共通のお財布箱』みたいなものがあればいいのに……!」
それをReactで実現してくれるのが、Context APIなんです。
—
2. Context APIという名の「共有ボックス」
Context APIを使うと、「わざわざ間にいる人たちを経由しなくても、必要なデータに直接アクセスできる秘密のパイプ」を作ることができます。
イメージとしては、リビングの真ん中に「お菓子の入った魔法の箱」を置くようなものです。
お父さんが箱をリビングに置き(Provider)、お母さんやお兄ちゃんを通さなくても、末っ子はいつでも直接その箱からお菓子を取り出すことができます(useContext)。
これですっきり解決!……と言いたいところですが、TypeScript(型付きのJavaScript)を使っている世界では、この魔法の箱を作るときに「この箱の中身は、こういうルール(型)のものしか入れちゃダメですよ!」と厳しく決めてあげる必要があります。
ここがつまづきやすいポイントなので、実際のコードと一緒に優しく見ていきましょう。
—
3. 実践!型安全なContext APIの作り方
今回は、「ログインしているユーザーの名前」をアプリの深い階層のコンポーネントにこっそり教える、というシチュエーションを考えてみます。
以下のコードをそのままコピーすれば、あなたの手元のエディタ(CodeSandboxやViteなど)でもすぐに動かせますよ。
ステップ1:Contextを作る(魔法の箱の設計図)
まずは、どんなデータを共有するのか、TypeScriptの「型」をしっかり定義して箱を作ります。
import React, { createContext, useContext, useState, ReactNode } from ‘react’;
// 1. 共有したいデータの「型」を定義します
// 「ユーザーの名前(文字列)」と「名前を変える関数」が入るよ、という設計図です
interface UserContextType {
userName: string;
setUserName: (name: string) => void;
}
// 2. Context(魔法の箱)を作ります
// 最初は中身が空っぽかもしれないので、undefinedを許容しておきます
const UserContext = createContext
ステップ2:Providerを作る(箱に中身を詰める係)
次に、この箱をアプリ全体(あるいは必要な範囲)に配るための「Provider(プロバイダー)」というコンポーネントを作ります。これがリビングにお菓子箱を置くお父さん役です。
// Providerが受け取る「子供たち(コンポーネント)」の型定義です
interface UserProviderProps {
children: ReactNode; // Reactで画面に表示できるものなら何でもOKという意味です
}
export const UserProvider: React.FC
// 共有したい状態(ステート)を定義
const [userName, setUserName] = useState
return (
// Providerで囲んだ下層のコンポーネントたちは、valueの中身にアクセスできるようになります
{children}
);
};
ステップ3:便利な専用フックを作る(ここがプロの技!)
`useContext`という標準機能はそのまま使うと、「今、箱の中身が空っぽじゃないよね?」というチェックを書く必要があって少し面倒です。
そこで、実務では「このカスタムフックを使えば、絶対に安全にデータが取れるよ」という専用の窓口(関数)を自作するのがお約束です。
// 3. 安全にデータを取り出すためのカスタムフック
export const useUser = () => {
const context = useContext(UserContext);
// うっかりProviderの外側でこのフックを呼んじゃった時への優しさ(エラーを教えてあげる)
if (context === undefined) {
throw new Error(‘useUserは、UserProviderの中だけで使ってくださいね!’);
}
return context;
};
—
4. 実際に深い階層からデータを呼び出してみよう!
準備は整いました。それでは、途中のコンポーネントを無視して、一番深ーいところにある孫コンポーネントから直接データを呼び出してみましょう。
// — 子や孫のコンポーネントたちがいる階層 —
// 途中のコンポーネント(お母さんや兄ちゃん):
// もうpropsをリレーする必要がないので、スッキリ!
const MiddleComponent = () => {
return (
ここは途中の階層です(propsのリレーはしていません!)
);
};
// 一番奥のコンポーネント(末っ子):
// さっき作った「useUser」を使うだけで、一発で名前を取り出せます!
const DeepChildComponent = () => {
const { userName, setUserName } = useUser(); // なんと、直接データを召喚!
return (
こんにちは、{userName}さん!
);
};
// — アプリの全体像をまとめる親 —
export default function App() {
return (
/ Providerでアプリ全体を包み込むことで、どこからでもデータにアクセス可能になります /
Context APIの型安全な使い方デモ
);
}
—
さいごに
いかがでしたでしょうか?
最初は「型定義」「Provider」「Context」「カスタムフック」と、覚えることが多くてクラクラしてしまったかもしれません。でも、一つひとつの役割を紐解いていくと、「コンポーネント同士のバケツリレーを無くして、みんながアクセスできる共有スペースを作るための仕組み」なんだなと分かっていただけたかと思います。
TypeScriptでしっかり型をつけておくと、エディタが「あ、そこスペルミスしてるよ」とか「そのプロパティは存在しないよ」と優しく教えてくれるので、コードを書くのが驚くほど安心で楽しくなりますよ。
もし最初につまずいても、「あ、今はリレーが面倒くさくなってきた合図だな、Contextを使ってみよう!」と思い出していただけたら、この記事を書いた私としてもこの上なく幸せです。
あなたのReactの旅が、これからもワクワクするものでありますように。一緒に一歩ずつ進んでいきましょうね!

コメント