paint-brush
React.js Context API で状態管理を簡素化する方法 - チュートリアル@codebucks
618 測定値
618 測定値

React.js Context API で状態管理を簡素化する方法 - チュートリアル

CodeBucks11m2024/08/02
Read on Terminal Reader

長すぎる; 読むには

このブログでは、Context API を使用して React で状態を管理するための包括的なガイドを提供します。prop ドリルを回避し、パフォーマンスを向上させ、Context API を効果的に実装する方法を説明します。実用的な例と最適化のヒントが紹介されており、React アプリケーションで状態管理を効率化したい開発者に最適です。
featured image - React.js Context API で状態管理を簡素化する方法 - チュートリアル
CodeBucks HackerNoon profile picture
0-item

こんにちは👋🏻、


この記事は、複数のコンポーネント間で状態を管理するためのより効果的な方法を知りたい初心者向けに特別に作成されています。また、コードの保守と理解を困難にする可能性のある、prop ドリルの一般的な問題に対処することも目的としています。まず、コンテキスト API がどのような問題を解決するかから始めましょう。


ビデオ形式の方がお好みの場合は、私の YouTube チャンネルで視聴できるチュートリアルがあります。👇🏻


プロップドリリングとは何ですか?

親コンポーネントから子コンポーネントにデータを渡す必要がある場合があり、その間の多数のコンポーネントを介して props を渡すことになることがあります。これはprop ドリリングと呼ばれ、すぐに面倒なことになります。これを明確にするために、例を見てみましょう。


React.js での Props ドリル

図に示すように、アプリケーションのルートにあるAppコンポーネントでデータを取得したとします。ここで、深くネストされたコンポーネント、たとえばGrandchildコンポーネントがこのデータにアクセスする必要がある場合、通常は、データがGrandchildに到達する前に、 ParentChildコンポーネントを介して props として渡されます。これは、アプリが大きくなるにつれて厄介な問題になる可能性があります。


もう一つの視覚的表現を次に示します。


Reactjs Props ドリルの例

上記の例では、 Profileコンポーネントにはユーザー データが必要ですが、このデータは、中間コンポーネント自体がデータを使用しない場合でも、まずAppコンポーネントとNavigationコンポーネントを通過する必要があります。では、これをどのようにクリーンアップすればよいでしょうか。ここで、Context API が役立ちます。


プロップドリル:

  • コンポーネントの再レンダリングを増加
  • 定型コードの増加
  • コンポーネントの依存関係を作成する
  • パフォーマンスが低下する

React コンテキスト API

React.js の Context API を使用すると、コンポーネント ツリーの各レベルにデータをプロパティとして渡すことなく、コンポーネント間でデータを渡すことができます。これは、コンテキスト オブジェクトで状態を定義するグローバル状態管理システムのように機能し、コンポーネント ツリーのどこからでも簡単にアクセスできます。例を使ってこれを理解しましょう。


React.js コンテキスト API

図からわかるように、複数のコンポーネントがアクセスするデータを格納するコンテキスト オブジェクトがあります。このデータは、API またはサードパーティのサービスから取得されます。任意のコンポーネントでこのコンテキスト データにアクセスする前に、このデータを必要とするすべてのコンポーネントをコンテキスト プロバイダー コンポーネントにラップする必要があります。


ナビゲーション コンポーネントとプロファイル コンポーネントのデータにのみアクセスする必要がある場合は、アプリ コンポーネントをラップする必要はありません。関連するコンポーネントをContextProviderでラップすると、コンテキスト データを使用する任意のコンポーネントでコンテキスト データに直接アクセスできます。まだ理解できなくても心配しないでください。コードを調べて、実際に動作を確認しましょう。


コンテキスト API の使用方法

まず、 Vite.jsを使用して React アプリを作成しましょう。以下のコマンドをコピーしてプロジェクトをセットアップするだけです。


 npm create vite@latest


  • プロジェクト名を追加
  • 反応を選択
  • オプションからTypescriptを選択
cd project_name // to change to project directory npm install npm run dev


次に、ブラウザで開発サーバーhttp://localhost:5173を開くことができます。


まず、必要なフォルダーを作成しましょう。これがプロジェクトのフォルダー構造です。

 src | components | context


コンポーネント フォルダーにProfile.jsxファイルを作成し、次のコードを追加します。

 import React from 'react' const Profile = () => { return ( <div>Profile</div> ) } export default Profile


コンポーネント フォルダーにNavbar.jsxというコンポーネントをもう 1 つ作成します。

 import Profile from './Profile' const Navbar = () => { return ( <nav style={{ display: "flex", justifyContent: "space-between", alignItems: "center", width: "90%", height: "10vh", backgroundColor: theme === "light" ? "#fff" : "#1b1b1b", color: theme === "light" ? "#1b1b1b" : "#fff", border: "1px solid #fff", borderRadius: "5px", padding: "0 20px", marginTop: "40px", }}> <h1>LOGO</h1> <Profile /> </nav> ) } export default Navbar


この<Navbar />コンポーネントをApp.jsxファイルにインポートしましょう。

 import Navbar from "./components/Navbar"; function App() { return ( <main style={{ display: "flex", flexDirection: "column", justifyContent: "start", alignItems: "center", height: "100vh", width: "100vw", }} > <Navbar /> </main> ); } export default App;


つまり、基本的に、 <Profile />コンポーネントは<Navbar />の子であり、 <Navbar /> <App />コンポーネントの子です。

コンテキストAPIの追加

contextフォルダーにUserContext.jsxファイルを作成しましょう。ファイルに次のコードを追加します。


 import { createContext, useEffect, useState } from "react"; export const UserContext = createContext(); export const UserProvider = ({ children }) => { const [user, setUser] = useState(null); const fetchUserData = async (id) => { const response = await fetch( `https://jsonplaceholder.typicode.com/users/${id}` ).then((response) => response.json()); console.log(response); setUser(response); }; useEffect(() => { fetchUserData(1); }, []); return ( <UserContext.Provider value={{ user, fetchUserData }} > {children} </UserContext.Provider> ); };


  • まず、 createContextを使用して空のUserContextオブジェクトを作成します。 reactからインポートするようにしてください。コンテキスト オブジェクト内にデフォルト値を追加できますが、今のところは null のままにしておきます。


  • 次に、 UserContext.Providerのように、 UserContextを使用してプロバイダーを返すUserProviderを作成します。これは子コンポーネントをラップし、値には子コンポーネントで使用したいものを何でも渡すことができます。


  • 現在、ユーザー データを取得するためにjsonplaceholder APIを使用しています。jsonplaceholder、テスト目的で偽の API エンドポイントを提供します。fetchUserData fetchUserDataidを受け入れ、その ID を使用してユーザー データを取得します。次に、応答をuser状態に保存します。


  • useEffectfetchUserData関数を呼び出しているので、ページの読み込み時に関数が呼び出され、 user状態にデータが挿入されます。


ここで、このコンテキストを<App />コンポーネントで使用してみましょう。 <UserProvider />を使用して<NavBar />コンポーネントをラップします。次のコードと同じです。

 <UserProvider> <Navbar /> </UserProvider>


<Profile />コンポーネントでuser状態を使用しましょう。そのためには、 useContextフックを使用します。これはUserContextを受け取り、 user状態やfetchUserData関数など、 UserProviderで渡した値を提供します。 <Profile />コンポーネントは既にプロバイダーでラップされている<Navbar />コンポーネント内にあるため、ラップする必要がないことに注意してください。


Profile.jsxを開き、次のコードを追加します。

 const { user } = useContext(UserContext); if (user) { return ( <span style={{ fontWeight: "bold", }} > {user.name} </span> ); } else { return <span>Login</span>; }


ここでは、 UserContextuser状態を使用しています。 userがいる場合はユーザー名を表示し、そうでない場合はログイン メッセージのみを表示します。出力を見ると、navbar コンポーネントにユーザー名があるはずです。これは、任意のコンポーネントのコンテキストにある任意の状態を直接使用する方法です。この状態を使用するコンポーネントは<Provider />内にラップする必要があります。


複数のコンテキストを使用することもできます。次の例に示すように、プロバイダー コンポーネントを別のプロバイダー コンポーネント内にラップするだけです。

 <ThemeProvider> <UserProvider> <Navbar /> </UserProvider> </ThemeProvider>


上記の例では、テーマの状態を管理する<ThemeProvider />を使用しています。


複数のコンテキスト プロバイダーを使用する完全な例については、上記の YouTube ビデオをご覧ください。

React Context API での再レンダリングの最適化

複数のコンポーネントで Context API を使用する場合に発生する問題が 1 つあります。Context API で状態または値が変更されるたびに、すべてのコンポーネントが変更された状態を使用していない場合でも、その特定のコンテキストにサブスクライブされているすべてのコンポーネントが再レンダリングされます。この再レンダリングの問題を理解するために、コンテキストを使用してカウント値を保存および表示する<Counter />コンポーネントを作成しましょう。


次の例を確認してください。コンポーネント フォルダーにCounter.jsxファイルを作成し、次のコードを貼り付けることができます。


 import { createContext, memo, useContext, useState } from "react"; const CountContext = createContext(); const CountProvider = ({ children }) => { const [count, setCount] = useState(0); return ( <CountContext.Provider value={{ count, setCount }}> {children} </CountContext.Provider> ); }; function CountTitle() { console.log("This is Count Title component"); return <h1>Counter Title</h1>; } function CountDisplay() { console.log("This is CountDisplay component"); const { count } = useContext(CountContext); return <div>Count: {count}</div>; } function CounterButton() { console.log("This is CounterButton component"); const { count, setCount } = useContext(CountContext); return ( <> <CountTitle /> <CountDisplay /> <button onClick={() => setCount(count + 1)}>Increase</button> </> ); } export default function Counter() { return ( <CountProvider> <CounterButton /> </CountProvider> ); }


上記のコードでは:

  • まず、createContext を使用して 1 つのCountContextオブジェクトを作成しますcreateContext.


  • CountProvider,カウント値を格納するための状態が 1 つあります。count とsetCountメソッドcount value プロパティを通じて子コンポーネントに送信しています。


  • 個々のコンポーネントが何回再レンダリングされるかを確認するために、コンポーネントを個別に作成しました。

    • <CountTitle /> : このコンポーネントはタイトルのみを表示し、コンテキストからの値も使用しません。

    • <CountDisplay /> : このコンポーネントはカウント値を表示し、コンテキストからのcount状態を使用します。

    • <CounterButton /> : このコンポーネントは、上記のコンポーネントと、 setCountを使用してカウント値を増やすボタンの両方をレンダリングします。


  • 最後に、他のコンポーネントがカウント値にアクセスできるように、 <CounterButton />コンポーネントをCountProviderコンポーネント内にラップします。


ここで、コードを実行してIncreaseボタンをクリックすると、状態が変化するたびにすべてのコンポーネントが再レンダリングされていることがログに表示されます。 <CountTitle />は count 値を使用していないにもかかわらず、再レンダリングされています。これは、 <CountTitle />の親コンポーネントである<CounterButton />が count の値を使用して更新しているために発生し、再レンダリングされるのです。


この動作を最適化するにはどうすればよいでしょうか。答えはmemoです。React のmemo使用すると、プロパティが変更されていない場合にコンポーネントの再レンダリングをスキップできます。 <CountTitle />コンポーネントの後に、次の行を追加しましょう。


 const MemoizedCountTitle = React.memo(CountTitle)


ここで、 <CounterButton />コンポーネントで<CountTitle />コンポーネントをレンダリングしているところで、次のコードのように<CountTitle /><MemoizedCountTitle />に置き換えます。


 <> <MemoizedCountTitle /> <CountDisplay /> <button onClick={() => setCount(count + 1)}>Increase</button> </>


ここで、カウントを増やしてログを確認すると、 <CountTitle />コンポーネントがレンダリングされなくなっていることがわかります。

Redux と Context API

Redux 、より予測可能な状態遷移を伴う複雑な状態管理のための状態管理ライブラリです。一方、Context API は、プロパティ ドリリングなしでコンポーネント ツリーを介してデータを渡すために、シンプルな状態管理用に設計されています。では、どちらを選択すればよいのでしょうか。


  • 状態が頻繁に変化しない場合は、 React Context APIを使用して、シンプルでローカライズされた状態管理を行います。


  • 複雑な状態管理のニーズには Redux を使用します。特に、構造化された状態管理の利点が追加のセットアップを上回る大規模なアプリケーションではReduxを使用します。


状態管理の一般的な選択肢としてもう 1 つライブラリがあります。それはReact Recoil です


  • React Recoil は、Context API のシンプルさと Redux のパワーおよびパフォーマンスを提供することを目的とした、React の状態管理ライブラリです。


React Recoilについてさらに詳しく知りたい場合は、コメントでお知らせください。皆さんのフィードバックに基づいて、このトピックに関する詳細なチュートリアルを作成します。

結論

React.js Context API は、複数のコンポーネントにまたがる状態を管理するための強力かつ効率的な方法を提供し、prop ドリルの問題に効果的に対処します。Context API を使用すると、コードを簡素化し、不要な再レンダリングを減らし、アプリケーション全体のパフォーマンスを向上させることができます。


Context API は単純な状態管理に最適ですが、より複雑なアプリケーションではReduxReact Recoilなどの他の状態管理ライブラリを使用すると効果的です。これらのツールをいつどのように使用するかを理解することで、より保守性と拡張性に優れた React アプリケーションを構築できるようになります。


この記事を読んでいただきありがとうございます。お役に立てれば幸いです。React、Redux、Next.js を使用したプロジェクトの学習と構築に興味がある場合は、こちらの YouTube チャンネルをご覧ください: CodeBucks


他にも読んでみたい記事がこちらにあります:

私の個人ブログをご覧ください: DevDreaming