コンテキストを使用してReactコンポーネント間で状態を共有する方法
著者は、 Creative Commons を選択して、 Write forDOnationsプログラムの一環として寄付を受け取りました。
序章
このチュートリアルでは、 Reactコンテキストを使用して、複数のコンポーネント間で状態を共有します。 Reactコンテキストは、データを props として明示的に渡すことなく、他のコンポーネントと情報を共有するためのインターフェースです。 これは、親コンポーネントと深くネストされた子コンポーネント間で情報を共有したり、サイト全体のデータを1つの場所に保存して、アプリケーションのどこからでもアクセスできることを意味します。 データとともに更新機能を提供することにより、ネストされたコンポーネントからデータを更新することもできます。
Reactコンテキストは、プロジェクトの集中状態管理システムとして使用するのに十分な柔軟性があります。または、アプリケーションのより小さなセクションにスコープを設定することもできます。 コンテキストを使用すると、追加のサードパーティツールを使用せずに、少量の構成でアプリケーション全体でデータを共有できます。 これにより、 Redux のようなツールに代わる軽量のツールが提供されます。これは、大規模なアプリケーションには役立ちますが、中規模のプロジェクトではセットアップが多すぎる可能性があります。
このチュートリアル全体を通して、コンテキストを使用して、さまざまなコンポーネント間で共通のデータセットを使用するアプリケーションを構築します。 これを説明するために、ユーザーがカスタムサラダを作成できるWebサイトを作成します。 Webサイトは、コンテキストを使用して、顧客情報、お気に入りのアイテム、およびカスタムサラダを保存します。 次に、そのデータにアクセスし、小道具を介してデータを渡すことなく、アプリケーション全体でデータを更新します。 このチュートリアルの終わりまでに、コンテキストを使用してプロジェクトのさまざまなレベルでデータを格納する方法と、ネストされたコンポーネントのデータにアクセスして更新する方法を学習します。
前提条件
- Node.jsを実行する開発環境が必要になります。 このチュートリアルは、Node.jsバージョン10.20.1およびnpmバージョン6.14.4でテストされました。 これをmacOSまたはUbuntu18.04にインストールするには、Node.jsをインストールしてmacOSにローカル開発環境を作成する方法またはのPPAを使用したインストール]セクションの手順に従います。 Ubuntu18.04にNode.jsをインストールする方法。
- Create React App でセットアップされたReact開発環境で、不要なボイラープレートが削除されています。 これを設定するには、ステップ1 —Reactクラスコンポーネントの状態を管理する方法のチュートリアルの空のプロジェクトを作成します。 このチュートリアルでは、プロジェクト名として
state-context-tutorial
を使用します。 - また、 JavaScriptでコーディングする方法にあるJavaScriptの基本的な知識と、HTMLおよびCSSの基本的な知識も必要です。 HTMLとCSSの便利なリソースは、 Mozilla DeveloperNetworkです。
- Reactコンポーネント、
useState
フック、およびuseReducer
フックを使用します。これらは、チュートリアルReactおよびでカスタムコンポーネントを作成する方法で学習できます。 ]Reactコンポーネントのフックで状態を管理する方法。
ステップ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, }
image
とname
は小道具になります。 このコードは、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()配列メソッドを使用してリスト内の各アイテムをマップし、name
とimage
を小道具として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
をインポートし、Navigation
とSaladMaker
をUserContext.Provider
というコンポーネントでラップしました。 この場合、UserContext
が標準のReactコンポーネントとして機能していることに注目してください。 このコンポーネントは、value
と呼ばれる単一の小道具を取ります。 その小道具は、共有したいデータになります。この場合は、user
オブジェクトです。
ファイルを保存して閉じます。 これで、データはアプリケーション全体で利用できるようになります。 ただし、データを使用するには、コンテキストをもう一度インポートしてアクセスする必要があります。
コンテキストを設定したので、コンポーネント内のハードコードされたデータを動的な値に置き換えることができます。 Navigation
のハードコードされた名前を、UserContext.Provider
で設定したユーザーデータに置き換えることから始めます。
Navigation.js
を開きます:
nano src/components/Navigation/Navigation.js
Navigation
内で、useContext
フックをReactからインポートし、UserContext
をコンポーネントディレクトリからインポートします。 次に、UserContext
を引数としてuseContext
を呼び出します。 UserContext.Provider
とは異なり、JSXでUserContext
をレンダリングする必要はありません。 フックは、value
小道具で提供したデータを返します。 name
とfavorites
を含むオブジェクトである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> ) }
UserContext
はApp.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
useContext
とUserContext
をインポートし、UserContext
でuseContext
を呼び出します。 その後、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 /> </> ) }
前の手順では、コンテキスト用に別のコンポーネントを作成しましたが、この場合は、使用しているのと同じファイルにコンポーネントを作成しています。 User
はApp
に直接関連していないように見えるので、それらを分離しておく方が理にかなっているかもしれません。 ただし、SaladContext
はSaladMaker
コンポーネントと密接に関連しているため、これらを一緒に保持すると、より読みやすいコードが作成されます。
さらに、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
を小道具として使用することです。 salad
とsetSalad
を個別に渡すことはできないため、これらを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>
要素に変換します。 必ずid
をkey
として使用してください。
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
という汎用コンテキストを作成して、データや名前の競合を気にせずに、コンポーネントの複数のポイントで使用できます。 SaladMaker
とSandwichMaker
がある場合、ツリーは次のようになります。
| App | Salads | OrderContext | SaladMaker | Sandwiches | OrderContext | SandwichMaker
OrderContext
が2回あることに注意してください。 useContext
フックが最寄りのプロバイダーを探すので、それで問題ありません。
このステップでは、コンテキストを使用してデータを共有および更新しました。 また、ルート要素の外側にコンテキストを配置して、ルートコンポーネントを乱雑にすることなく、情報を必要とするコンポーネントの近くに配置しました。 最後に、コンテキストと状態管理フックを組み合わせて、動的で複数のコンポーネント間でアクセス可能なデータを作成しました。
結論
コンテキストは、アプリケーション全体でデータを保存および使用する機能を提供する強力で柔軟なツールです。 追加のサードパーティによるインストールや構成を必要としない組み込みツールを使用して、分散データを処理する機能を提供します。
再利用可能なコンテキストを作成することは、要素間でデータにアクセスする必要があるフォームや、タブと表示の両方に共通のコンテキストを必要とするタブビューなど、さまざまな共通コンポーネントで重要です。 テーマ、フォームデータ、アラートメッセージなど、さまざまな種類の情報をコンテキストに保存できます。 コンテキストを使用すると、中間コンポーネントを介してデータを渡す方法や、ストアを大きくしすぎずに中央ストアにデータを保存する方法を気にせずに、データにアクセスできるコンポーネントを自由に構築できます。
Reactのチュートリアルをもっと見たい場合は、 Reactトピックページを確認するか、React.jsシリーズのコーディング方法ページに戻ってください。