【デイトラ学習記録】DAY34 コンテキスト(Context)

今回はコンテキストについて学習していきます。

Reactにおけるコンテキスト(Context)とは?

  • Reactでは、コンポーネントが親→子、 子→孫 へとPropsを使ってデータを渡すのが基本の流れになっている
  • アプリが大きくなると複数のコンポーネント間でデータのやりとりが生じるため、Propsを使ってバケツリレーで渡すのが効率悪いし、コードも汚い
  • そこでコンテキストを使うことで、Propsを使わずに、コンポーネント全体にデータを渡すことが可能!

コンテキストの基本

  • データをグローバルに使いたい時に使用する
  • 具体使用例
    • ライトモード / ダークモード
    • 認証情報
    • ユーザー情報
    • etc

コンテキスト使用の手順

  1. コンテキストの作成
    • React.createContext を使ってコンテキスト作成
  2. コンテキストプロバイダで値を提供
    • コンテキストを使用するコンポーネントツリーの上位に、Context.Providerを配置、値を提供
  3. コンテキストを利用
    • コンテキストの値を使いたいコンポーネントで、useContext フックで値を取得

【ログイン状態の実装例】基本的な使い方

コンテキストの作成 と コンテキストプロバイダで値を提供

  • React.createContext を使ってコンテキスト作成
  • コンテキストを使用するコンポーネントツリーの上位に、Context.Providerを配置、値を提供
src/js/contexts/AuthContext.tsx
type AuthContextType = {
  isLoggedIn: boolean;
};

export const AuthContext = createContext<AuthContextType>({
  isLoggedIn: true,
});

export const AuthProvider = ({ children }: PropsWithChildren) => {
  const isLoggedIn = true;
  return <AuthContext.Provider value={{ isLoggedIn }}>{children}</AuthContext.Provider>;
};

コンテコンテキストを利用するコンポーネントをchildrenに配置する

src/js/index.tsx
<AuthProvider>
  <App />
</AuthProvider>

コンテコンテキストを利用

  • コンテキストの値を使いたいコンポーネントで、useContext フックで値を取得
src/js/App.tsx
export const App = () => {
  const { isLoggedIn } = useContext(AuthContext);
  return <div>{isLoggedIn ? "ログイン" : "ログアウト"}</div>;
};

【ログイン状態の実装例】Propsからコンテキストを操作する

コンテキストの作成 と コンテキストプロバイダで値を提供

  • isLoggedInuseState で管理
src/js/contexts/AuthContext.tsx
type AuthContextType = {
  isLoggedIn: boolean;
  setIsLoggedIn: React.Dispatch<React.SetStateAction<boolean>>;
};

export const AuthContext = createContext<AuthContextType>({
  isLoggedIn: true,
  setIsLoggedIn: () => {},
});

export const AuthProvider = ({ children }: PropsWithChildren) => {
  const [isLoggedIn, setIsLoggedIn] = useState<boolean>(false);

  return (
    <AuthContext.Provider value={{ isLoggedIn: isLoggedIn, setIsLoggedIn: setIsLoggedIn }}>
      {children}
    </AuthContext.Provider>
  );
};

コンテコンテキストを利用

  • Propsからコンテキストを操作する
src/js/App.tsx
export const App = () => {
  const { isLoggedIn, setIsLoggedIn } = useContext(AuthContext);

  return (
      <label className="flex gap-2 p-2 border w-fit">
        <input
          type="checkbox"
          checked={isLoggedIn}
          onClick={() => setIsLoggedIn(!isLoggedIn)}
        />
        <span>{isLoggedIn ? "ログイン" : "ログアウト"}</span>
      </label>
  );
};

【ログイン状態の実装例】ログアウト中はログイン画面を表示する

本来はサーバーサイドのログインAPIなどがあればいいが、今回は名前が入力されればログイン状態とする。

Context(グローバルに使えるデータ) で、userNameを管理する

  • userName ・・・ ログインユーザーを保持する情報
  • setUserName ・・・ ログインユーザーを useState で管理する
src/js/contexts/AuthContext.tsx
type AuthContextType = {
  isLoggedIn: boolean;
  setIsLoggedIn: React.Dispatch<React.SetStateAction<boolean>>;
  userName: string;
  setUserName: React.Dispatch<React.SetStateAction<string>>;
};

export const AuthContext = createContext<AuthContextType>({
  isLoggedIn: false,
  setIsLoggedIn: () => {},
  userName: "",
  setUserName: () => {},
});

export const AuthProvider = ({ children }: PropsWithChildren) => {
  const [isLoggedIn, setIsLoggedIn] = useState<boolean>(false);
  const [userName, setUserName] = useState<string>("");

  return (
    <AuthContext.Provider
      value={{
        isLoggedIn: isLoggedIn,
        setIsLoggedIn: setIsLoggedIn,
        userName: userName,
        setUserName: setUserName,
      }}
    >
      {children}
    </AuthContext.Provider>
  );
};

ログイン状態の有無(isLoggedIn)で、表示画面を切り替える

src/js/App.tsx
export const App = () => {
  // useContextを使って、AuthContextの値を取得
  const { isLoggedIn, setIsLoggedIn, userName, setUserName } = useContext(AuthContext);

  // isLoggedInの有無でログイン状態を切り替える
  if (!isLoggedIn) {
    return (
      <main>
        <h1>ログイン</h1>
        <TextField
          label="ユーザー名"
          type="text"
          value={userName} // userNameの値を表示
          onChange={setUserName} // userNameの値を変更するためのsetUserNameを呼び出す
        ></TextField>
        {/* ログインボタンクリックで、ログイン状態(isLogdin: true)へ切り替える */}
        <ButtonRegister onClick={() => setIsLoggedIn(true)}>ログイン</ButtonRegister>
      </main>
    );
  }

  // isLoggedInの有無でログイン状態を切り替える
  return (
    <main>
      <h1>TODOリスト</h1>
      <p>ログインユーザー:{userName}</p>
      {/* ログアウトボタンクリックで、ログアウト状態(isLogdin: false)へ切り替える */}
      <ButtonRegister onClick={() => setIsLoggedIn(false)}>ログアウト</ButtonRegister>
      {/* TODOアプリのUI */}
    </main>
  );
};

setIsLoggedIn を切り替えているだけなので、画面の情報が残るのを解消する

別ファイルでuseContext(AuthContext) を呼び出し、AuthContext 経由でログイン状態 と ログアウト状態 を定義する

src/js/contexts/use-auth.ts
export const useAuth = () => {
  // AuthContextから取得した値を返す
  const { isLoggedIn, setIsLoggedIn, userName, setUserName } = useContext(AuthContext);

  // ログイン状態を返す
  const login = () => {
    // ユーザー名が入力されている場合、ログイン状態に切り替える
    if (!userName) {
      alert("ユーザー名を入力してください");
      return;
    }
    setIsLoggedIn(true);
  };

  // ログアウト状態を返す
  const logout = () => {
    // ログアウトされたら、ユーザー名を空にしてログアウト状態に切り替える
    setIsLoggedIn(false), setUserName("");
  };

  return { isLoggedIn, userName, setUserName, login, logout };
};

Appで、ログインボタンとログアウトボタンがクリックされたときに、use-auth.ts で定義した、loginlogout を実行する

TSX
export const App = () => {
  // const { isLoggedIn, setIsLoggedIn, userName, setUserName } = useContext(AuthContext);
  const { isLoggedIn, login, logout, userName, setUserName } = useAuth();

  // isLoggedInの有無でログイン状態を切り替える
  if (!isLoggedIn) {
    return (
      <main className="flex flex-col mx-auto p-4 gap-5">
        <Heading label={"h1"}>ログイン</Heading>
          <TextField
            label="ユーザー名"
            type="text"
            value={userName}
            onChange={setUserName}
          ></TextField>
          {/* ログインボタンクリックで、ログイン状態(isLogdin: true)へ切り替える */}
          <ButtonRegister onClick={login}>ログイン</ButtonRegister>
      </main>
    );
  }

  // isLoggedInの有無でログイン状態を切り替える
  return (
    <main className="flex flex-col mx-auto p-4 gap-5">
      <Heading label={"h1"}>TODOリスト</Heading>
      <p>ログインユーザー:{userName}</p>
      {/* ログアウトボタンクリックで、ログアウト状態(isLogdin: false)へ切り替える */}
      <ButtonRegister className="bg-red-500" onClick={logout}>
        ログアウト
      </ButtonRegister>
    </main>
  );
};

ログインユーザとTODOの担当者が一致した場合には、ハイライトする処理を追加する

TSX
export const TodoItem = ({ id, task, person, deadline, handleDelete }: Props) => {
  // useAuth()から、userNameを使えるようにする
  const { userName } = useAuth();
  {
    // ログインユーザー(userName) と {person} が一致した場合にハイライト
  const style = userName === person ? "bg-red-100" : "";
  return (
    <li className={`py-4 border border-b-gray-100 ${style}`}>
      <p>タスク:{task}</p>
      <p>担当者:{person}</p>
      <p>締切:{deadline}</p>
      <ButtonDelete
        onClick={() => {
          handleDelete(id);
        }}
      >
        削除
      </ButtonDelete>
    </li>
  );
};

ローカルストレージに保存する

src/js/contexts/use-auth.ts
const USER_NAME_KEY = "user-name";

export const useAuth = () => {
  // AuthContextから取得した値を返す
  const { isLoggedIn, setIsLoggedIn, userName, setUserName } = useContext(AuthContext);

  // ログイン状態を返す
  const login = () => {
    // ユーザー名が入力されている場合、ログイン状態に切り替える
    if (!userName) {
      alert("ユーザー名を入力してください");
      return;
    }
    setIsLoggedIn(true);
    localStorage.setItem(USER_NAME_KEY, userName);
  };

  // ログアウト状態を返す
  const logout = () => {
    // ログアウトされたら、ユーザー名を空にしてログアウト状態に切り替える
    setIsLoggedIn(false);
    setUserName("");
    localStorage.removeItem(USER_NAME_KEY);
  };

  // マウント時にローカルストレージからユーザー名が取得できた場合には、ログイン中として処理する
  useEffect(() => {
    const userNameData = localStorage.getItem(USER_NAME_KEY);
    // ローカルストレージの値が取得できた場合
    if (userNameData) {
      // ログイン中として扱う処理
      setUserName(userNameData);
      setIsLoggedIn(true);
    }
  }, []);

  return { isLoggedIn, userName, setUserName, login, logout };
};

ディレクトリ構成を修正

dev-react-practice
└ js
  ├ components
  │ ├ parts
  │ │ └ 〇〇.tsx
  │ └ todo
  │   └ 〇〇.tsx
  ├ contexts
  │ └ 〇〇.tsx
  ├ hooks
  │ └ 〇〇.tsx
  ├ pages
  │ └ 〇〇.tsx
  ├ types
  │ └ 〇〇.tsx
  ├ App.tsx
  ├ index.tsx
  └

公式サイトのおすすめ構成もみてみる

開発リポジトリ

まとめ

コンテキスト(Context)の概念について学習してきました。

  • Propsでバケツリレーすることなく、サイト全体でグローバルにデータを管理することができる

感想

コンテキスト(Context)の応用で様々な機能を追加しました。
色々な機能を追加したけど、使っているのはこれまでの学習内容なので、基本の理解が改めて重要に思いました。

  • Context 経由で、カスタムフック (Custom Hook)を作る
  • ログイン / ログアウト状態を管理する
    • useEffect → 副作用(データ取得、DOM 操作)を実行する
    • useState → 状態管理
  • ブラウザに保存されたデータを使う
    • localstorage

Custom Hook / useEffect / useState / localstorage この辺を曖昧にしておくと、応用で詰まるので基本の理解を深めつつす進めていきたいです。

Yuta | Code.Yu

WordPressをメインに活動する、フリーランスのWeb制作コーダーです。
React案件を経験したことをきっかけに、さらにフロントエンド開発のスキルを高めるため、JavaScriptやReactの学習を進めています。このブログでは、学習の過程や記録を発信しています。

Web制作に関する情報はこちら
Code.Yu | ホームページ制作・コーディング代行 ↗︎