見出し画像

React contextは便利

Reactの便利機能に、contextというものがある。
データの管理・受け渡しを行うという点では、useStateと同じである。
useStateで管理しているデータは、propsとして他のコンポーネントに渡せる。useStateと違う点は、対象のコンポーネントにデータを直接渡せる事である。
特に力を発揮するのは、異なる階層と深さのコンポーネントへ効率的にデータのやり取りを行いたい時である。

個人開発中のアプリケーションでも、初めはuseStateのみでコンポーネント間のやり取りを行なっていた。しかし、コンポーネントの数が増えるにつれuseStateの数も増えるので、次第に状態・データ管理が困難になっていった。また、複数のコンポーネント間でとなると、更に複雑になっていく。
それが積み重なった結果、結局どのuseStateで何を管理しているのかを把握するのが難しくなる。そうすると、当然開発速度も低下するので効率が下がり、機能追加やレイアウト変更を行う以前の問題になってしまった。
このように、該当データを不必要なコンポーネントにも受け渡しの為にpropsを逐一渡さなければならない事をprops drilling(プロップドリリング)と言う(個人的にはバケツリレーを想像すると判り易いと思う)。詳しくは、公式サイトに記述があるので参照して欲しい。

これではいけないと思い、各コンポーネントに散乱していたuseStateを極力一箇所で集中管理し、必要なコンポーネントにだけデータを渡したいと考えるに至った。
これを実現するには、まず以下のようにcontextを作成する必要がある。データを格納するためのコンテナと考えれば想像し易いだろう。

// サンプルコード
// 個人的には、型定義の管理をtype.ts等のファイルで一括管理して、
// exportで使用する方がより効率的だと思う

import React, { createContext, useContext, useState } from "react";

interface UserContextProps {
  username: string;
  setUsername: (name: string) => void;
}

const UserContext = createContext<UserContextProps | undefined>(undefined);

export const UserProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const [username, setUsername] = useState<string>("");

  return (
    <UserContext.Provider value={{ username, setUsername }}>
      {children}
    </UserContext.Provider>
  );
};

更に、以下のエラーハンドリングに対するカスタムフックを同ファイル内に記述する事で、contextをより簡潔に使用出来るようになる。このカスタムフックを記述しない場合、contextを使用する全コンポーネントでエラーハンドリングに対する記述が必要になる。

import React, { createContext, useContext, useState } from "react";

interface UserContextProps {
  username: string;
  setUsername: (name: string) => void;
}

const UserContext = createContext<UserContextProps | undefined>(undefined);

export const UserProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const [username, setUsername] = useState<string>("");

  return (
    <UserContext.Provider value={{ username, setUsername }}>
      {children}
    </UserContext.Provider>
  );
};

// 下記を忘れず記述すること👇
export const useUserContext = (): UserContextProps => {
  const context = useContext(UserContext);

  if (!context) {
    throw new Error("useUserContext must be used within a UserProvider");
  }
  return context;
};

次に、使用したいコンポーネントでは、以下のように記述する事でcontextを通してデータ取得を行える。

import React from "react";
import { useUserContext } from "./UserContext";

const UserProfile: React.FC = () => {
  const { username, setUsername } = useUserContext();

  // ここでセット関数(setUsername)に入力内容のデータを渡す事で、
  // 状態関数(username)が入力の度に更新・取得される
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setUsername(e.target.value);
  };

  return (
    <div>
      <h1>Welcome, {username || "Guest"}!</h1>
      <input
        type="text"
        placeholder="Enter your name"
        value={username}
        onChange={handleChange}
      />
    </div>
  );
};

サンプルではあるが、上記のような形でcontextを利用する事で、props drillingを防ぎつつより効率的にデータ管理を行う事が出来る。
少ないコンポーネント間ならuseStateだけで管理、関係するコンポーネントが多ければcontextで管理、状況に応じて適切に活用していきたい。

いいなと思ったら応援しよう!