※このブログではサーバー運用、技術の検証等の費用のため広告をいれています。
記事が見づらいなどの問題がありましたらContactからお知らせください。


【React in TypeScript】Contextの使い方とユースケースについて解説

react javascript redux DependencyIndection Context TypeScript

投稿日:2021年2月10日

このエントリーをはてなブックマークに追加
ContextはReactのv16.3から利用できるようになった機能で、これを利用することであらゆるコンポーネントから特定のObjectにアクセスできるようになります。 この記事ではContextの実例とReduxとの使い分けについて解説しています。

はじめに

Contextとは

ContextはReactのv16.3から利用できるようになった機能で、これを利用することであらゆるコンポーネントから特定のObjectにアクセスできるようになります。

この記事ではContextの実例とReduxとの使い分けについて解説しています。

環境

この記事は以下の環境を前提にしています。

  • React==v17.0.1
  • typescript==4.1.3

実践

Contextの作成

まずはじめにコンテキストを作成する必要があります。

コンテキストの作成はcreateContext()メソッドを使います。

./context.tsx
import React from 'react';

export const SampleTextContext = React.createContext("aaaaa");

createContext()の引数はContextの初期値です。

今回は適当にaaaaaという文字列にしました。

次に値を利用したいコンポーネント側の実装を見ていきましょう!

関数コンポーネントでの利用

関数コンポーネントで値を取得する場合には、コンテキストのConsumerタグを使います。

./components/MyFunctionalComponents.tsx
import React from "react";
import { SampleTextContext } from "../contexts";

const MyFunctionalComponents = () => {
    return (
        <SampleTextContext.Consumer>
            {value => {
                return(
                    <div>{value}</div>
                )
            }}
        </SampleTextContext.Consumer>
    )
}

export default MyFunctionalComponents;

Consumerタグのchildren要素はコンテキストの値を受け取ってReactノードを返す関数である必要があります。

valueの値が変わると自動的に再レンダリングされます。

クラスコンポーネントでの利用

次にクラスコンポーネントでコンテキストの値を利用する場合について説明します。

まずは前準備として静的フィールドのcontextTypeを利用したいコンテキストに設定します。

これによってクラスコンポーネントはcontextフィールドに対象のコンテキストの値をサブスクライブするようになります。

./components/MyClassComponent.tsx
import React from "react";
import {SampleTextContext} from "../contexts";

export default class MyClassComponents extends React.Component<{},{text:string}>{
    static contextType = SampleTextContext;
    render(){
        return (
            <div>
               {this.context}
            </div>
        )
    }
}

Providerの提供

コンポーネントがContextの値を読み取るには、そのコンポーネントの階層関係の親要素にあたるどこかでContextのProviderタグを呼び出す必要があります。

ここでは、上で作成した2つのコンポーネントをProviderで包んだ上で、更にContextの値を変更するボタンを作成してみます。

./App.tsx
import React from 'react';
import MyClassComponent from "./components/MyClassComponents";
import MyFunctionalComponent from "./components/MyFunctionalComponent";
import {SampleTextContext} from "./contexts";



function App() {
  const [sampleText, setSampleText] = React.useState("aaaa");

  return (
    <SampleTextContext.Provider value={sampleText}>
      <button onClick={()=>{setSampleText(sampleText + "a")}}>ボタン</button>
      <div className="App">
        <MyClassComponent />
        <MyFunctionalComponent />
      </div>
    </SampleTextContext.Provider>
  );
}

export default App;

ボタンをクリックする度にSampleTextContextの値が変更されます。

子要素のMyClassComponentMyFunctionalComponentは値の変更をサブスクライブし、再レンダリングされることを確認できると思います。


ContextとReduxの使い分け

ここでContextのメリット、デメリットからReduxとの使い分けを考えてみます。

メリット1:導入と管理が簡単

Reduxと比較してContextは状態の導入と管理が非常に簡単です。

特に追加のパッケージなどを利用せずとも、基本的に以下の3手順で利用することができます。

  1. Contextの作成
  2. コンポーネント側で利用Contextの設定
  3. コンポーネントの親要素にProviderを配置

デメリット1:レンダリングコスト

Reduxと違い、Contextで値を利用した際にはコンポーネントのすべてのDOM要素が再レンダリングされます。

Contextの値が頻繁に変更される場合と、ContextのProvider内のコンポーネントの範囲が広い場合、再レンダリングのコストは非常に大きくなってしまいます。

デメリット2:複数Contextの利用の際の煩雑さ

複数のContextを同一箇所で利用しようとした場合に、おそらく頭を悩ませることになると思います。

まず関数コンポーネントの場合以下のように無駄にネストが深くなってしまいます。

複数Contextを利用しようとした関数コンポーネントの例
function Content() {
  return (
    <ThemeContext.Consumer>
      {theme => (
        <UserContext.Consumer>
          {user => (
            <ProfilePage user={user} theme={theme} />
          )}
        </UserContext.Consumer>
      )}
    </ThemeContext.Consumer>
  );
}

またクラスコンポーネントの場合にはさらに問題は大きく、

通常の方法では複数Contextの利用すらできません。

結論

以上のContextのメリット、デメリットから

以下のような場合にContextを使用するのがいいのではないでしょうか?

  1. 小さいアプリケーションでのDI
  2. 値がめったに変化しないObject(ユーザー設定や特定の季節・イベントのテーマなど)

このエントリーをはてなブックマークに追加


関連記事

記事へのコメント