今回はuseContextを見ていきます。
公式のreferenceでもいいのですが、今回はコンテクストで深くデータを受け渡すを見ていこうと思います。
(公式を追いながら、感じたことを含めてアウトプットしています。コードは飛び飛びですのであしからず...。)
通常、親コンポーネントから子コンポーネントには props を使って情報を渡します。しかし、props を多数の中間コンポーネントを経由して渡さないといけない場合や、アプリ内の多くのコンポーネントが同じ情報を必要とする場合、props の受け渡しは冗長で不便なものとなり得ます。コンテクスト (Context) を使用することで、親コンポーネントから props を明示的に渡さずとも、それ以下のツリー内の任意のコンポーネントが情報を受け取れるようにできます。
いわゆるバケツリレーで渡していくのは冗長ですよね。
渡したいコンポーネントに辿り着くまでに、経由するcomponent全てが情報知っている状態にもなります。
コンテクスト内では共通して使えるようにしておく、と言うことですね。
もし props を受け渡すことなしに、データを必要としているツリー内のコンポーネントに直接データを「テレポート」させる方法があれば、素晴らしいと思いませんか? React のコンテクスト機能を使えば、それが可能です!
えっ!?propsを使わずにコンポーネントにデータを!?
できらぁ!
例を見る
level を props としてそれぞれの に個別に渡しています。
// 公式から引用
<Section>
<Heading level={3}>About</Heading>
<Heading level={3}>Photos</Heading>
<Heading level={3}>Videos</Heading>
</Section>
下記のようにして、Headingを操作できればスッキリしそうです。
<Section level={3}>
<Heading>About</Heading>
<Heading>Photos</Heading>
<Heading>Videos</Heading>
</Section>
コンポーネントは、最も近い
<Section>
のレベルをどうやって知ることができるのでしょうか? これには、ツリー上部のどこかにあるデータを子コンポーネントが「要求する」方法が必要です。
Headingはなんとかして、Sectionの値を知りたいです。(Headinigに値をテレポートさせたい)
そんな時にコンテクスト
が登場します。
ステップ 1:コンテクストを作成する
まずはコンテクストを作成する必要があります。複数のコンポーネントから使うことができるように、ファイルを作ってコンテクストをエクスポートする必要があります。
なるほど、では指定してみます。
// 公式から引用
// LevelContext.js
import { createContext } from 'react';
export const LevelContext = createContext(1);
createContextに渡した 1
というのがテレポートさせる値です。
(この時点ではまだuseContextは登場しません。)
createContext の唯一の引数はデフォルト値です。ここでは、1 という値は最大の見出しに対応するレベルを示していますが、任意の型の値(オブジェクトでも)を渡すことができます。デフォルト値の意味は次のステップで分かります。
createContextの引数(default値)に1を渡しましたが、何でも渡せそうです。
ステップ 2:コンテクストを使用する
この例はまだちゃんと動作していません。すべての見出しが同じサイズになってしまっているのは、コンテクストの使用はしているが、まだコンテクストの提供を行っていないからです。React はどこから値を取得すればいいのか分かりません!
現在、createContextに初期値として入れた1を呼び出せてはいますが、、
// 公式から引用
import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';
export default function Heading({ children }) {
const level = useContext(LevelContext); // 絶対1になる
switch (level) {
case 1:
return <h1>{children}{level}</h1>;
case 2:
return <h2>{children}{level}</h2>;
case 3:
return <h3>{children}{level}</h3>;
case 4:
return <h4>{children}{level}</h4>;
case 5:
return <h5>{children}{level}</h5>;
case 6:
return <h6>{children}{level}</h6>;
default:
throw Error('Unknown level: ' + level);
}
}
createContextに値を渡して、それをuseContextの中に入れて使うじゃ足りないんですね。
コンテクストの提供というものをすればいいのか、どうすればSectionに指定した値を呼べるのだろう...
ステップ 3:コンテクストを提供する
ステップ2までの実装では、
LevelContext -> Heading.jsx
と値が渡されていました。これを
LevelContext -> Section.jsx -> Heading.jsx
とすれば良さそうです。
+ import { LevelContext } from './LevelContext.js';
export default function Section({ level, children }) {
return (
<section className="section">
+ <LevelContext.Provider value={level}>
{children}
+ </LevelContext.Provider>
</section>
);
}
LevelContext.Provider というものが出てきました。(プロバイダって翻訳すると何?)
これを行うことで、LevelContext.Provider配下にいるcomponentに対して コンテクストの提供
が可能となります。
いったんまとめます。
porpsで値をバケツリレーせず、共通した値を孫にも渡したい
- useContextを使う
- 準備する
const hoge = createContext(初期値)
- 提供する
<hoge.Provider value={値}>
で囲む (親側で渡したい値を指定) - 呼び出す
const huga = useContext(hoge)
値を呼び出す
- 準備する
...準備がややこしいんですよね!!!!
ただ、仕組みはわかりました!
もちろんオブジェクトも渡せます
import { createContext, useContext } from 'react';
const ThemeContext = createContext(null);
export default function MyApp() {
const theme = {
mode : "dark",
font : 20
}
return (
<ThemeContext.Provider value={theme}>
<Form />
</ThemeContext.Provider>
)
}
まとめ
useContextを使うべく、公式を追ってみました。
準備が必要という時点でちょっと複雑に感じますが、わかってしまえば大丈夫ですね!
もう一歩深掘るには createContext
を理解する必要がありそうです!
(何を返すのか、中身がどうなっているのか)
次回は createContext
を追ってみることにします!