コンテキストを使用してReactコンポーネント間で状態を共有する方法

提供:Dev Guides
移動先:案内検索

著者は、 Creative Commons を選択して、 Write forDOnationsプログラムの一環として寄付を受け取りました。

序章

このチュートリアルでは、 Reactコンテキストを使用して、複数のコンポーネント間で状態を共有します。 Reactコンテキストは、データを props として明示的に渡すことなく、他のコンポーネントと情報を共有するためのインターフェースです。 これは、親コンポーネントと深くネストされた子コンポーネント間で情報を共有したり、サイト全体のデータを1つの場所に保存して、アプリケーションのどこからでもアクセスできることを意味します。 データとともに更新機能を提供することにより、ネストされたコンポーネントからデータを更新することもできます。

Reactコンテキストは、プロジェクトの集中状態管理システムとして使用するのに十分な柔軟性があります。または、アプリケーションのより小さなセクションにスコープを設定することもできます。 コンテキストを使用すると、追加のサードパーティツールを使用せずに、少量の構成でアプリケーション全体でデータを共有できます。 これにより、 Redux のようなツールに代わる軽量のツールが提供されます。これは、大規模なアプリケーションには役立ちますが、中規模のプロジェクトではセットアップが多すぎる可能性があります。

このチュートリアル全体を通して、コンテキストを使用して、さまざまなコンポーネント間で共通のデータセットを使用するアプリケーションを構築します。 これを説明するために、ユーザーがカスタムサラダを作成できるWebサイトを作成します。 Webサイトは、コンテキストを使用して、顧客情報、お気に入りのアイテム、およびカスタムサラダを保存します。 次に、そのデータにアクセスし、小道具を介してデータを渡すことなく、アプリケーション全体でデータを更新します。 このチュートリアルの終わりまでに、コンテキストを使用してプロジェクトのさまざまなレベルでデータを格納する方法と、ネストされたコンポーネントのデータにアクセスして更新する方法を学習します。

前提条件

ステップ1—アプリケーションの基盤を構築する

このステップでは、カスタムサラダビルダーの一般的な構造を構築します。 可能なトッピング、選択したトッピングのリスト、および顧客情報を表示するコンポーネントを作成します。 静的データを使用してアプリケーションを構築すると、さまざまなコンポーネントでさまざまな情報がどのように使用されているか、およびコンテキストで役立つデータを特定する方法がわかります。

作成するアプリケーションの例を次に示します。

コンポーネント間で使用する必要のある情報があることに注意してください。 たとえば、ユーザー名(このサンプルでは Kwame )は、ナビゲーション領域にユーザーデータを表示しますが、お気に入りのアイテムを識別するため、またはチェックアウトページ用にユーザー情報が必要になる場合もあります。 ユーザー情報は、アプリケーション内の任意のコンポーネントからアクセスできる必要があります。 サラダビルダー自体を見ると、各サラダ材料は画面下部の Your Salad リストを更新できる必要があるため、そのデータを次の場所から保存して更新する必要があります。各コンポーネントにもアクセスできます。

アプリの構造を理解できるように、すべてのデータをハードコーディングすることから始めます。 後で、次のステップからコンテキストを追加します。 アプリケーションが成長し始めると、コンテキストが最大の価値を提供するため、このステップでは、コンポーネントツリー全体でコンテキストがどのように機能するかを示すために、いくつかのコンポーネントを構築します。 小さなコンポーネントやライブラリの場合、多くの場合、ラッピングコンポーネントと、 ReactHooksクラスベースの管理などの低レベルの状態管理手法を使用できます。

複数のコンポーネントを含む小さなアプリを作成しているため、 JSS をインストールして、クラス名の競合が発生しないようにし、コンポーネントと同じファイルにスタイルを追加できるようにします。 JSSの詳細については、Reactコンポーネントのスタイリングを参照してください。

次のコマンドを実行します。

npm install react-jss

npmはコンポーネントをインストールし、完了すると次のようなメッセージが表示されます。

Output+ [email protected]
added 27 packages from 10 contributors, removed 10 packages andaudited 1973 packages in 15.507s

JSSがインストールされたので、必要なさまざまなコンポーネントを検討します。 ページの上部には、ウェルカムメッセージを保存するためのNavigationコンポーネントがあります。 次のコンポーネントはSaladMaker自体になります。 これは、ビルダーと下部の YourSaladリストとともにタイトルを保持します。 材料のあるセクションは、SaladBuilderと呼ばれる別のコンポーネントになり、SaladMaker内にネストされます。 各成分は、SaladItemコンポーネントのインスタンスになります。 最後に、一番下のリストはSaladSummaryというコンポーネントになります。

注:コンポーネントをこのように分割する必要はありません。 アプリケーションで作業するとき、機能を追加するにつれて構造が変化し、進化します。 この例は、コンテキストがツリー内のさまざまなコンポーネントにどのように影響するかを調べるための構造を提供することを目的としています。


必要なコンポーネントがわかったので、それぞれのディレクトリを作成します。

mkdir src/components/Navigation
mkdir src/components/SaladMaker
mkdir src/components/SaladItem
mkdir src/components/SaladBuilder
mkdir src/components/SaladSummary

次に、Navigationから始めてコンポーネントを上から下に構築します。 まず、コンポーネントファイルをテキストエディタで開きます。

nano src/components/Navigation/Navigation.js

Navigationというコンポーネントを作成し、スタイルを追加してNavigationに境界線とパディングを付けます。

state-context-tutorial / src / components / Navigation / Navigation.js

import React from 'react';
import { createUseStyles } from 'react-jss';

const useStyles = createUseStyles({
  wrapper: {
    borderBottom: 'black solid 1px',
    padding: [15, 10],
    textAlign: 'right',
  }
});

export default function Navigation() {
  const classes = useStyles();
  return(
    <div className={classes.wrapper}>
      Welcome, Kwame
    </div>
  )
}

JSSを使用しているため、CSSファイルではなく、コンポーネントで直接スタイルオブジェクトを作成できます。 ラッパーdivには、パディングとsolid blackの境界線があり、テキストをtextAlignに合わせて右側に配置します。

ファイルを保存して閉じます。 次に、プロジェクトのルートであるApp.jsを開きます。

nano src/components/App/App.js

Navigationコンポーネントをインポートし、強調表示された行を追加して、空のタグ内にレンダリングします。

state-context-tutorial / src / components / App / App.js

import React from 'react';
import Navigation from '../Navigation/Navigation';

function App() {
  return (
    <>
      <Navigation />
    </>
  );
}

export default App;

ファイルを保存して閉じます。 これを行うと、ブラウザが更新され、ナビゲーションバーが表示されます。

ナビゲーションバーはグローバルコンポーネントと考えてください。この例では、ナビゲーションバーがすべてのページで再利用されるテンプレートコンポーネントとして機能しているためです。

次のコンポーネントはSaladMaker自体になります。 これは、特定のページまたは特定の状態でのみレンダリングされるコンポーネントです。

テキストエディタでSaladMaker.jsを開きます。

nano src/components/SaladMaker/SaladMaker.js

<h1>タグの見出しが付いたコンポーネントを作成します。

state-context-tutorial / src / components / SaladMaker / SaladMaker.js

import React from 'react';
import { createUseStyles } from 'react-jss';

const useStyles = createUseStyles({
  wrapper: {
    textAlign: 'center',
  }
});

export default function SaladMaker() {
  const classes = useStyles();
  return(
    <>
      <h1 className={classes.wrapper}>
        <span role="img" aria-label="salad">🥗 </span>
          Build Your Custom Salad!
          <span role="img" aria-label="salad"> 🥗</span>
      </h1>
    </>
  )
}

このコードでは、textAlignを使用してコンポーネントをページの中央に配置しています。 span要素のroleおよびaria-label属性は、 Accessible Rich Internet Applications(ARIA)を使用したアクセシビリティに役立ちます。

ファイルを保存して閉じます。 App.jsを開いて、コンポーネントをレンダリングします。

nano src/components/App/App.js

SaladMakerをインポートし、Navigationコンポーネントの後にレンダリングします。

state-context-tutorial / src / components / App / App.js

import React from 'react';
import Navigation from '../Navigation/Navigation';
import SaladMaker from '../SaladMaker/SaladMaker';

function App() {
  return (
    <>
      <Navigation />
      <SaladMaker />
    </>
  );
}

export default App;

ファイルを保存して閉じます。 これを行うと、ページが再読み込みされ、見出しが表示されます。

次に、SaladItemというコンポーネントを作成します。 これは、個々の材料ごとのカードになります。

テキストエディタでファイルを開きます。

nano src/components/SaladItem/SaladItem.js

このコンポーネントは、アイテムの名前、アイテムがユーザーのお気に入りかどうかを示すアイコン、クリックするとサラダにアイテムを追加するボタン内に配置された絵文字の3つの部分で構成されます。 SaladItem.jsに次の行を追加します。

state-context-tutorial / src / components / SaladItem / SaladItem.js

import React from 'react';
import PropTypes from 'prop-types';
import { createUseStyles } from 'react-jss';

const useStyles = createUseStyles({
  add: {
    background: 'none',
    border: 'none',
    cursor: 'pointer',
  },
  favorite: {
    fontSize: 20,
    position: 'absolute',
    top: 10,
    right: 10,
  },
  image: {
    fontSize: 80
  },
  wrapper: {
    border: 'lightgrey solid 1px',
    margin: 20,
    padding: 25,
    position: 'relative',
    textAlign: 'center',
    textTransform: 'capitalize',
    width: 200,
  }
});

export default function SaladItem({ image, name }) {
  const classes = useStyles();
  const favorite = true;
  return(
    <div className={classes.wrapper}>
        <h3>
          {name}
        </h3>
        <span className={classes.favorite} aria-label={favorite ? 'Favorite' : 'Not Favorite'}>
          {favorite ? '😋' : ''}
        </span>
        <button className={classes.add}>
          <span className={classes.image} role="img" aria-label={name}>{image}</span>
        </button>
    </div>
  )
}

SaladItem.propTypes = {
  image: PropTypes.string.isRequired,
  name: PropTypes.string.isRequired,
}

imagenameは小道具になります。 このコードは、favorite変数と三元演算子を使用して、favoriteアイコンが表示されるかどうかを条件付きで判別します。 favorite変数は、後でユーザーのプロファイルの一部としてコンテキストを使用して決定されます。 今のところ、trueに設定してください。 スタイリングにより、お気に入りのアイコンがカードの右上隅に配置され、ボタンのデフォルトの境界線と背景が削除されます。 wrapperクラスは小さな境界線を追加し、テキストの一部を変換します。 最後に、 PropTypes は、弱いタイピングシステムを追加して、間違ったプロップタイプが渡されないようにするための強制を提供します。

ファイルを保存して閉じます。 次に、さまざまなアイテムをレンダリングする必要があります。 これは、SaladBuilderというコンポーネントを使用して行います。このコンポーネントには、一連のSaladItemコンポーネントに変換されるアイテムのリストが含まれています。

SaladBuilderを開きます:

nano src/components/SaladBuilder/SaladBuilder.js

これが本番アプリの場合、このデータは多くの場合、アプリケーションプログラミングインターフェイス(API)から取得されます。 しかし今のところ、成分のハードコードされたリストを使用してください:

state-context-tutorial / src / components / SaladBuilder / SaladBuilder.js

import React from 'react';
import SaladItem from '../SaladItem/SaladItem';

import { createUseStyles } from 'react-jss';

const useStyles = createUseStyles({
  wrapper: {
    display: 'flex',
    flexWrap: 'wrap',
    padding: [10, 50],
    justifyContent: 'center',
  }
});

const ingredients = [
  {
    image: '🍎',
    name: 'apple',
  },
  {
    image: '🥑',
    name: 'avocado',
  },
  {
    image: '🥦',
    name: 'broccoli',
  },
  {
    image: '🥕',
    name: 'carrot',
  },
  {
    image: '🍷',
    name: 'red wine dressing',
  },
  {
    image: '🍚',
    name: 'seasoned rice',
  },
];

export default function SaladBuilder() {
  const classes = useStyles();
  return(
    <div className={classes.wrapper}>
      {
        ingredients.map(ingredient => (
          <SaladItem
            key={ingredient.name}
            image={ingredient.image}
            name={ingredient.name}
          />
        ))
      }
    </div>
  )
}

このスニペットは、 map()配列メソッドを使用してリスト内の各アイテムをマップし、nameimageを小道具としてSaladItemコンポーネントに渡します。 。 マップするときは、必ず各アイテムにキーを追加してください。 このコンポーネントのスタイリングは、flexboxレイアウトflexの表示を追加し、コンポーネントをラップして中央に配置します。

ファイルを保存して閉じます。

最後に、コンポーネントをSaladMakerでレンダリングして、ページに表示されるようにします。

SaladMakerを開きます:

nano src/components/SaladMaker/SaladMaker.js

次に、SaladBuilderをインポートし、見出しの後にレンダリングします。

state-context-tutorial / src / components / SaladMaker / SaladMaker.js

import React from 'react';
import { createUseStyles } from 'react-jss';
import SaladBuilder from '../SaladBuilder/SaladBuilder';

const useStyles = createUseStyles({
  wrapper: {
    textAlign: 'center',
  }
});

export default function SaladMaker() {
  const classes = useStyles();
  return(
    <>
      <h1 className={classes.wrapper}>
        <span role="img" aria-label="salad">🥗 </span>
          Build Your Custom Salad!
          <span role="img" aria-label="salad"> 🥗</span>
      </h1>
      <SaladBuilder />
    </>
  )
}

ファイルを保存して閉じます。 これを行うと、ページがリロードされ、コンテンツが見つかります。

最後のステップは、進行中のサラダの要約を追加することです。 このコンポーネントは、ユーザーが選択したアイテムのリストを表示します。 今のところ、アイテムをハードコーディングします。 手順3でコンテキストを使用して更新します。

テキストエディタでSaladSummaryを開きます。

nano src/components/SaladSummary/SaladSummary.js

コンポーネントは、見出しと並べ替えられていないアイテムのリストになります。 フレックスボックスを使用して、それらをラップさせます。

state-context-tutorial / src / components / SaladSummary / SaladSummary.jss

import React from 'react';
import { createUseStyles } from 'react-jss';

const useStyles = createUseStyles({
  list: {
    display: 'flex',
    flexDirection: 'column',
    flexWrap: 'wrap',
    maxHeight: 50,
    '& li': {
      width: 100
    }
  },
  wrapper: {
    borderTop: 'black solid 1px',
    display: 'flex',
    padding: 25,
  }
});

export default function SaladSummary() {
  const classes = useStyles();
  return(
    <div className={classes.wrapper}>
      <h2>Your Salad</h2>
      <ul className={classes.list}>
        <li>Apple</li>
        <li>Avocado</li>
        <li>Carrots</li>
      </ul>
    </div>
  )
}

ファイルを保存します。 次に、SaladMakerを開いて、アイテムをレンダリングします。

nano src/components/SaladMaker/SaladMaker.js

SaladBuilderの後にSaladSummaryをインポートして追加します。

state-context-tutorial / src / components / SaladMaker / SaladMaker.js

import React from 'react';
import { createUseStyles } from 'react-jss';
import SaladBuilder from '../SaladBuilder/SaladBuilder';
import SaladSummary from '../SaladSummary/SaladSummary';

const useStyles = createUseStyles({
  wrapper: {
    textAlign: 'center',
  }
});

export default function SaladMaker() {
  const classes = useStyles();
  return(
    <>
      <h1 className={classes.wrapper}>
        <span role="img" aria-label="salad">🥗 </span>
          Build Your Custom Salad!
          <span role="img" aria-label="salad"> 🥗</span>
      </h1>
      <SaladBuilder />
      <SaladSummary />
    </>
  )
}

ファイルを保存して閉じます。 これを行うと、ページが更新され、完全なアプリケーションが見つかります。

アプリケーション全体で共有データがあります。 NavigationコンポーネントとSaladItemコンポーネントはどちらも、ユーザーについて何かを知っている必要があります。名前とお気に入りのリストです。 SaladItemは、SaladSummaryコンポーネントでアクセス可能なデータも更新する必要があります。 コンポーネントは共通の祖先を共有しますが、ツリーを介してデータを渡すことは困難であり、エラーが発生しやすくなります。

そこで、コンテキストが登場します。 共通の親でデータを宣言し、コンポーネントの階層に明示的に渡すことなく、後でアクセスできます。

このステップでは、ユーザーがオプションのリストからサラダを作成できるようにするアプリケーションを作成しました。 他のコンポーネントによって制御されるデータにアクセスまたは更新する必要があるコンポーネントのセットを作成しました。 次のステップでは、コンテキストを使用してデータを保存し、子コンポーネントにアクセスします。

ステップ2—ルートコンポーネントからのデータの提供

このステップでは、コンテキストを使用して、コンポーネントのルートに顧客情報を保存します。 カスタムコンテキストを作成してから、プロジェクトのルートに情報を格納するProviderと呼ばれる特別なラッピングコンポーネントを使用します。 次に、 useContext Hook を使用して、ネストされたコンポーネントでプロバイダーに接続し、静的情報を表示できるようにします。 このステップを完了すると、情報の集中ストアを提供し、さまざまなコンポーネントのコンテキストに格納されている情報を使用できるようになります。

最も基本的なコンテキストは、情報を共有するためのインターフェイスです。 多くのアプリケーションには、ユーザー設定、テーマ情報、サイト全体のアプリケーション変更など、アプリケーション全体で共有する必要のあるユニバーサル情報がいくつかあります。 コンテキストを使用すると、その情報をルートレベルで保存し、どこからでもアクセスできます。 親に情報を設定するので、それは常に利用可能であり、常に最新であることがわかります。

コンテキストを追加するには、Userという名前の新しいディレクトリを作成します。

mkdir src/components/User

Userは、useContextと呼ばれる特別なフックのコンポーネントとしてもデータとしても使用するという点で、従来のコンポーネントにはなりません。 今のところ、フラットなファイル構造を維持しますが、多くのコンテキストを使用する場合は、それらを別のディレクトリ構造に移動する価値があるかもしれません。

次に、テキストエディタでUser.jsを開きます。

nano src/components/User/User.js

ファイル内で、ReactからcreateContext関数をインポートし、関数を実行して結果をエクスポートします。

state-context-tutorial / src / components / User / User.js

import { createContext } from 'react';

const UserContext = createContext();
export default UserContext;

関数を実行することにより、コンテキストを登録しました。 結果、UserContextは、コンポーネントで使用するものです。

ファイルを保存して閉じます。

次のステップは、コンテキストを要素のセットに適用することです。 そのためには、Providerというコンポーネントを使用します。 Providerは、データを設定してから、いくつかの子コンポーネントをラップするコンポーネントです。 ラップされた子コンポーネントは、useContextフックを使用してProviderからのデータにアクセスできます。

ユーザーデータはプロジェクト全体で一定であるため、コンポーネントツリーのできるだけ高い位置に配置します。 このアプリケーションでは、Appコンポーネントのルートレベルに配置します。

Appを開きます:

nano src/components/App/App.js

次の強調表示されたコード行を追加して、コンテキストをインポートし、データを渡します。

state-context-tutorial / src / components / App / App.js

import React from 'react';
import Navigation from '../Navigation/Navigation';
import SaladMaker from '../SaladMaker/SaladMaker';
import UserContext from '../User/User';

const user = {
  name: 'Kwame',
  favorites: [
    'avocado',
    'carrot'
  ]
}

function App() {
  return (
    <UserContext.Provider value={user}>
      <Navigation />
      <SaladMaker />
    </UserContext.Provider>
  );
}

export default App;

一般的なアプリケーションでは、ユーザーデータをフェッチするか、サーバー側のレンダリング中にデータを保存します。 この場合、APIから受け取る可能性のあるいくつかのデータをハードコーディングしました。 ユーザー名を文字列として保持するuserというオブジェクトと、お気に入りの材料の配列を作成しました。

次に、UserContextをインポートし、NavigationSaladMakerUserContext.Providerというコンポーネントでラップしました。 この場合、UserContextが標準のReactコンポーネントとして機能していることに注目してください。 このコンポーネントは、valueと呼ばれる単一の小道具を取ります。 その小道具は、共有したいデータになります。この場合は、userオブジェクトです。

ファイルを保存して閉じます。 これで、データはアプリケーション全体で利用できるようになります。 ただし、データを使用するには、コンテキストをもう一度インポートしてアクセスする必要があります。

コンテキストを設定したので、コンポーネント内のハードコードされたデータを動的な値に置き換えることができます。 Navigationのハードコードされた名前を、UserContext.Providerで設定したユーザーデータに置き換えることから始めます。

Navigation.jsを開きます:

nano src/components/Navigation/Navigation.js

Navigation内で、useContextフックをReactからインポートし、UserContextをコンポーネントディレクトリからインポートします。 次に、UserContextを引数としてuseContextを呼び出します。 UserContext.Providerとは異なり、JSXUserContextをレンダリングする必要はありません。 フックは、value小道具で提供したデータを返します。 namefavoritesを含むオブジェクトであるuserという新しい変数にデータを保存します。 次に、ハードコードされた名前をuser.nameに置き換えることができます。

state-context-tutorial / src / components / Navigation / Navigation.js

import React, { useContext } from 'react';
import { createUseStyles } from 'react-jss';

import UserContext from '../User/User';

const useStyles = createUseStyles({
  wrapper: {
    outline: 'black solid 1px',
    padding: [15, 10],
    textAlign: 'right',
  }
});

export default function Navigation() {
  const user = useContext(UserContext);
  const classes = useStyles();
  return(
    <div className={classes.wrapper}>
      Welcome, {user.name}
    </div>
  )
}

UserContextApp.jsのコンポーネントとして機能しましたが、ここではデータの一部としてより多く使用しています。 ただし、必要に応じて、コンポーネントとして機能することもできます。 UserContextの一部であるConsumerを使用して同じデータにアクセスできます。 JSXにUserContext.Consumerを追加してデータを取得し、関数を子として使用してデータにアクセスします。

Consumerコンポーネントを使用することは可能ですが、フックを使用すると、同じ最新情報を提供しながら、多くの場合、短くて読みやすくなります。 これが、このチュートリアルでフックアプローチを使用する理由です。

ファイルを保存して閉じます。 これを行うと、ページが更新され、同じ名前が表示されます。 しかし、今回は動的に更新されました。

この場合、データは多くのコンポーネント間を移動しませんでした。 データが移動したパスを表すコンポーネントツリーは、次のようになります。

| UserContext.Provider
  | Navigation

このユーザー名を小道具として渡すことができ、この規模では効果的な戦略になる可能性があります。 ただし、アプリケーションが大きくなると、Navigationコンポーネントが移動する可能性があります。 NavigationコンポーネントとTitleBarなどの別のコンポーネントをラップするHeaderというコンポーネントがある場合もあれば、Templateコンポーネントを作成して次に、そこにNavigationをネストします。 コンテキストを使用すると、Providerがツリー上にある限り、Navigationをリファクタリングする必要がなくなり、リファクタリングが容易になります。

ユーザーデータを必要とする次のコンポーネントは、SaladItemコンポーネントです。 SaladItemコンポーネントでは、ユーザーのお気に入りの配列が必要になります。 材料がユーザーのお気に入りである場合は、条件付きで絵文字を表示します。

SaladItem.jsを開きます:

nano src/components/SaladItem/SaladItem.js

useContextUserContextをインポートし、UserContextuseContextを呼び出します。 その後、includesメソッドを使用して、材料がfavorites 配列にあるかどうかを確認します。

state-context-tutorial / src / components / SaladItem / SaladItem.js

import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import { createUseStyles } from 'react-jss';

import UserContext from '../User/User';

const useStyles = createUseStyles({
...
});

export default function SaladItem({ image, name }) {
  const classes = useStyles();
  const user = useContext(UserContext);
  const favorite = user.favorites.includes(name);
  return(
    <div className={classes.wrapper}>
        <h3>
          {name}
        </h3>
        <span className={classes.favorite} aria-label={favorite ? 'Favorite' : 'Not Favorite'}>
          {favorite ? '😋' : ''}
        </span>
        <button className={classes.add}>
          <span className={classes.image} role="img" aria-label={name}>{image}</span>
        </button>
    </div>
  )
}

SaladItem.propTypes = {
  image: PropTypes.string.isRequired,
  name: PropTypes.string.isRequired,
}

ファイルを保存して閉じます。 すると、ブラウザが更新され、お気に入りのアイテムだけに絵文字が表示されます。

Navigationとは異なり、コンテキストははるかに遠くまで進んでいます。 コンポーネントツリーは次のようになります。

| User.Provider
  | SaladMaker
    | SaladBuilder
      | SaladItem

情報は、小道具なしで2つの中間コンポーネントをスキップしました。 ツリー全体でデータをプロップとして渡す必要がある場合、それは多くの作業になり、将来の開発者がコードをリファクタリングして、プロップを渡すのを忘れるリスクがあります。 状況に応じて、アプリケーションの成長と進化に合わせてコードが機能することを確信できます。

このステップでは、コンテキストを作成し、Providerを使用してコンポーネントツリーにデータを設定しました。 また、useContextフックを使用してコンテキストにアクセスし、複数のコンポーネント間でコンテキストを使用しました。 このデータは静的であるため、初期設定後に変更されることはありませんが、データを共有したり、複数のコンポーネント間でデータを変更したりする必要がある場合があります。 次のステップでは、コンテキストを使用してネストされたデータを更新します。

ステップ3—ネストされたコンポーネントからのデータの更新

このステップでは、コンテキストとuseReducerフックを使用して、ネストされたコンポーネントが消費および更新できる動的データを作成します。 SaladItemコンポーネントを更新して、SaladSummaryが使用および表示するデータを設定します。 また、ルートコンポーネントの外部にコンテキストプロバイダーを設定します。 この手順を完了すると、複数のコンポーネント間でデータを使用および更新できるアプリケーションが作成され、アプリケーションのさまざまなレベルで複数のコンテキストプロバイダーを追加できるようになります。

この時点で、アプリケーションは複数のコンポーネントにわたってユーザーデータを表示していますが、ユーザーとの対話はありません。 前の手順では、コンテキストを使用して単一のデータを共有しましたが、関数を含むデータのコレクションを共有することもできます。 つまり、データを共有したり、データを更新する機能を共有したりすることができます。

アプリケーションでは、各SaladItemが共有リストを更新する必要があります。 次に、SaladSummaryコンポーネントは、ユーザーが選択したアイテムを表示し、それをリストに追加します。 問題は、これらのコンポーネントが直接の子孫ではないため、データと更新機能を小道具として渡すことができないことです。 しかし、それらは共通の親を共有しています:SaladMaker

コンテキストとReduxなどの他の状態管理ソリューションとの大きな違いの1つは、コンテキストが中央ストアになることを意図していないことです。 アプリケーション全体で複数回使用し、ルートレベルまたはコンポーネントツリーの奥深くで開始できます。 つまり、競合を心配することなく、アプリケーション全体にコンテキストを広げて、焦点を絞ったデータコレクションを作成できます。

コンテキストに焦点を合わせ続けるには、可能な場合は最も近い共有の親をラップするProvidersを作成します。 この場合、つまり、Appに別のコンテキストを追加するのではなく、SaladMakerコンポーネントにコンテキストを追加します。

SaladMakerを開きます:

nano src/components/SaladMaker/SaladMaker.js

次に、SaladContextという新しいコンテキストを作成してエクスポートします。

state-context-tutorial / src / components / SaladMaker / SaladMaker.js

import React, { createContext } from 'react';
import { createUseStyles } from 'react-jss';
import SaladBuilder from '../SaladBuilder/SaladBuilder';
import SaladSummary from '../SaladSummary/SaladSummary';

const useStyles = createUseStyles({
  wrapper: {
    textAlign: 'center',
  }
});

export const SaladContext = createContext();

export default function SaladMaker() {
  const classes = useStyles();
  return(
    <>
      <h1 className={classes.wrapper}>
        <span role="img" aria-label="salad">🥗 </span>
          Build Your Custom Salad!
          <span role="img" aria-label="salad"> 🥗</span>
      </h1>
      <SaladBuilder />
      <SaladSummary />
    </>
  )
}

前の手順では、コンテキスト用に別のコンポーネントを作成しましたが、この場合は、使用しているのと同じファイルにコンポーネントを作成しています。 UserAppに直接関連していないように見えるので、それらを分離しておく方が理にかなっているかもしれません。 ただし、SaladContextSaladMakerコンポーネントと密接に関連しているため、これらを一緒に保持すると、より読みやすいコードが作成されます。

さらに、OrderContextと呼ばれるより一般的なコンテキストを作成して、複数のコンポーネント間で再利用することもできます。 その場合は、別のコンポーネントを作成する必要があります。 今のところ、それらを一緒に保ちます。 別のパターンに移行する場合は、後でいつでもリファクタリングできます。

Providerを追加する前に、共有するデータについて考えてください。 アイテムの配列とアイテムを追加するための関数が必要になります。 他の一元化された状態管理ツールとは異なり、コンテキストはデータの更新を処理しません。 後で使用するためのデータを保持するだけです。 データを更新するには、フックなどの他の状態管理ツールを使用する必要があります。 同じコンポーネントのデータを収集する場合は、useStateまたはuseReducerフックのいずれかを使用します。 これらのフックを初めて使用する場合は、Reactコンポーネントのフックを使用して状態を管理する方法を確認してください。

useReducerフックは、すべてのアクションで最新の状態を更新する必要があるため、最適です。

state配列に新しいアイテムを追加するreducer関数を作成し、useReducerフックを使用してsalad配列と[ X137X]関数:

state-context-tutorial / src / components / SaladMaker / SaladMaker.js

import React, { useReducer, createContext } from 'react';
import { createUseStyles } from 'react-jss';
import SaladBuilder from '../SaladBuilder/SaladBuilder';
import SaladSummary from '../SaladSummary/SaladSummary';

const useStyles = createUseStyles({
  wrapper: {
    textAlign: 'center',
  }
});

export const SaladContext = createContext();

function reducer(state, item) {
  return [...state, item]
}

export default function SaladMaker() {
  const classes = useStyles();
  const [salad, setSalad] = useReducer(reducer, []);
  return(
    <>
      <h1 className={classes.wrapper}>
        <span role="img" aria-label="salad">🥗 </span>
          Build Your Custom Salad!
          <span role="img" aria-label="salad"> 🥗</span>
      </h1>
      <SaladBuilder />
      <SaladSummary />
    </>
  )
}

これで、共有するsaladデータ、データを更新するsetSaladという関数、および同じコンポーネントでデータを共有するSaladContextを含むコンポーネントができました。 。 この時点で、それらを組み合わせる必要があります。

組み合わせるには、Providerを作成する必要があります。 問題は、Providerが単一のvalueを小道具として使用することです。 saladsetSaladを個別に渡すことはできないため、これらを1つのオブジェクトに結合し、オブジェクトをvalueとして渡す必要があります。

state-context-tutorial / src / components / SaladMaker / SaladMaker.js

import React, { useReducer, createContext } from 'react';
import { createUseStyles } from 'react-jss';
import SaladBuilder from '../SaladBuilder/SaladBuilder';
import SaladSummary from '../SaladSummary/SaladSummary';

const useStyles = createUseStyles({
  wrapper: {
    textAlign: 'center',
  }
});

export const SaladContext = createContext();

function reducer(state, item) {
  return [...state, item]
}

export default function SaladMaker() {
  const classes = useStyles();
  const [salad, setSalad] = useReducer(reducer, []);
  return(
    <SaladContext.Provider value={{ salad, setSalad }}>
      <h1 className={classes.wrapper}>
        <span role="img" aria-label="salad">🥗 </span>
          Build Your Custom Salad!
          <span role="img" aria-label="salad"> 🥗</span>
      </h1>
      <SaladBuilder />
      <SaladSummary />
    </SaladContext.Provider>
  )
}

ファイルを保存して閉じます。 Navigationと同様に、SaladSummaryがコンテキストと同じコンポーネントにある場合、コンテキストを作成する必要がないように見える場合があります。 saladを小道具として渡すことは完全に合理的ですが、後でリファクタリングすることになる可能性があります。 ここでコンテキストを使用すると、情報が1か所にまとめられます。

次に、SaladItemコンポーネントに移動し、setSalad関数をコンテキストから引き出します。

コンポーネントをテキストエディタで開きます。

nano src/components/SaladItem/SaladItem.js

SaladItem内で、SaladMakerからコンテキストをインポートし、破棄を使用してsetSalad関数を引き出します。 setSalad関数を呼び出すボタンにクリックイベントを追加します。 ユーザーがアイテムを複数回追加できるようにするため、map関数が一意のkeyを割り当てることができるように、アイテムごとに一意のIDを作成する必要もあります。 ]:

state-context-tutorial / src / components / SaladItem / SaladItem.js

import React, { useReducer, useContext } from 'react';
import PropTypes from 'prop-types';
import { createUseStyles } from 'react-jss';

import UserContext from '../User/User';
import { SaladContext } from '../SaladMaker/SaladMaker';

const useStyles = createUseStyles({
...
});

const reducer = key => key + 1;
export default function SaladItem({ image, name }) {
  const classes = useStyles();
  const { setSalad } = useContext(SaladContext)
  const user = useContext(UserContext);
  const favorite = user.favorites.includes(name);
  const [id, updateId] = useReducer(reducer, 0);
  function update() {
    setSalad({
      name,
      id: `${name}-${id}`
    })
    updateId();
  };
  return(
    <div className={classes.wrapper}>
        <h3>
          {name}
        </h3>
        <span className={classes.favorite} aria-label={favorite ? 'Favorite' : 'Not Favorite'}>
          {favorite ? '😋' : ''}
        </span>
        <button className={classes.add} onClick={update}>
          <span className={classes.image} role="img" aria-label={name}>{image}</span>
        </button>
    </div>
  )
}
...

一意のIDを作成するには、useReducerフックを使用して、クリックするたびに値をインクリメントします。 最初のクリックでは、IDは0になります。 2番目は1というようになります。 この値をユーザーに表示することはありません。 これにより、後でマッピング関数の一意の値が作成されます。

一意のIDを作成した後、updateという関数を作成して、IDをインクリメントし、setSaladを呼び出します。 最後に、onClickプロペラを使用して機能をボタンにアタッチしました。

ファイルを保存して閉じます。 最後のステップは、SaladSummaryのコンテキストから動的データをプルすることです。

SaladSummaryを開きます:

nano src/components/SaladSummary/SaladSummary.js

SaladContextコンポーネントをインポートし、destructuringを使用してsaladデータを引き出します。 ハードコードされたリストアイテムをsaladにマップする関数に置き換え、オブジェクトを<li>要素に変換します。 必ずidkeyとして使用してください。

state-context-tutorial / src / components / SaladSummary / SaladSummary.js

import React, { useContext } from 'react';
import { createUseStyles } from 'react-jss';

import { SaladContext } from '../SaladMaker/SaladMaker';

const useStyles = createUseStyles({
...
});

export default function SaladSummary() {
  const classes = useStyles();
  const { salad } = useContext(SaladContext);
  return(
    <div className={classes.wrapper}>
      <h2>Your Salad</h2>
      <ul className={classes.list}>
        {salad.map(({ name, id }) => (<li key={id}>{name}</li>))}
      </ul>
    </div>
  )
}

ファイルを保存して閉じます。 そうすると、アイテムをクリックできるようになり、概要が更新されます。

コンテキストによって、さまざまなコンポーネントでデータを共有および更新する機能がどのように提供されたかに注目してください。 コンテキストはアイテム自体を更新しませんでしたが、複数のコンポーネント間でuseReducerフックを使用する方法を提供しました。 さらに、ツリーの下位にコンテキストを配置する自由もありました。 コンテキストを常にルートに保持するのが最善のように思えるかもしれませんが、コンテキストを低く保つことで、中央ストアに未使用の状態が残ることを心配する必要がなくなります。 コンポーネントをアンマウントするとすぐに、データは消えます。 データを保存したい場合は問題になる可能性がありますが、その場合は、コンテキストを上位の親に上げる必要があります。

アプリケーションツリーの下位にあるコンテキストを使用するもう1つの利点は、競合を気にせずにコンテキストを再利用できることです。 サンドイッチメーカーとサラダメーカーを備えたより大きなアプリがあるとします。 OrderContextという汎用コンテキストを作成して、データや名前の競合を気にせずに、コンポーネントの複数のポイントで使用できます。 SaladMakerSandwichMakerがある場合、ツリーは次のようになります。

| App
  | Salads
    | OrderContext
      | SaladMaker
  | Sandwiches
    | OrderContext
      | SandwichMaker

OrderContextが2回あることに注意してください。 useContextフックが最寄りのプロバイダーを探すので、それで問題ありません。

このステップでは、コンテキストを使用してデータを共有および更新しました。 また、ルート要素の外側にコンテキストを配置して、ルートコンポーネントを乱雑にすることなく、情報を必要とするコンポーネントの近くに配置しました。 最後に、コンテキストと状態管理フックを組み合わせて、動的で複数のコンポーネント間でアクセス可能なデータを作成しました。

結論

コンテキストは、アプリケーション全体でデータを保存および使用する機能を提供する強力で柔軟なツールです。 追加のサードパーティによるインストールや構成を必要としない組み込みツールを使用して、分散データを処理する機能を提供します。

再利用可能なコンテキストを作成することは、要素間でデータにアクセスする必要があるフォームや、タブと表示の両方に共通のコンテキストを必要とするタブビューなど、さまざまな共通コンポーネントで重要です。 テーマ、フォームデータ、アラートメッセージなど、さまざまな種類の情報をコンテキストに保存できます。 コンテキストを使用すると、中間コンポーネントを介してデータを渡す方法や、ストアを大きくしすぎずに中央ストアにデータを保存する方法を気にせずに、データにアクセスできるコンポーネントを自由に構築できます。

Reactのチュートリアルをもっと見たい場合は、 Reactトピックページを確認するか、React.jsシリーズのコーディング方法ページに戻ってください。