※このブログではサーバー運用、技術の検証等の費用のため広告をいれています。
記事が見づらいなどの問題がありましたらContactからお知らせください。


【React + TypeScript】Contextでログイン状態・認証情報を管理

react web開発 フロント開発 TypeScript

投稿日:2021年5月30日

このエントリーをはてなブックマークに追加
この記事ではContextでログイン状態・認証情報を管理する方法について詳しく解説しています。以前に自分がYoutubeに投稿した『React勉強垂れ流し動画』内で同じ方法で実装しました。

はじめに

この記事について

この記事ではContextでログイン状態・認証情報を管理する方法について詳しく解説しています。以前に自分がYoutubeに投稿した『React勉強垂れ流し動画』内で同じ方法で実装しました。

環境について

この記事は以下の環境で確認されました。

  • react version 17.0.2
  • typescript version 4.3.2

参考ソースコード


実践

作成する機能

まずは作成する機能の条件を列挙しておきます。

  1. ログイン状態をbooleanで管理
  2. 以前にログインしたことがあれば自動で認証情報を取得
  3. どのコンポーネントからでもログイン状態、認証情報を取得可能
  4. どのコンポーネントからでもログイン・ログアウトが可能

条件の3を満たす方法はいくつかあるのですが、今回はReactのデフォルトの機能であるContextを使用してみます。

Contextの作成

まずはログイン状態と認証情報を取得するContextをAuthContextProvider.tsxというファイルに作成してみます。Contextの作成はReact.createContext<T>()関数を使用します。

AuthContextProvider.tsx
import React, { useEffect, useState } from "react";

/**
 * 簡易的な認証情報の型のサンプル
 */
type AuthInfo = {
  userId: string;
};

// ログイン状態のContext
export const LoggedInContext = React.createContext<boolean>(false);

// 認証情報と認証情報セットのContext
export const AuthInfoContext = React.createContext<
  [AuthInfo, React.Dispatch<React.SetStateAction<AuthInfo>>]
>([{ userId: "" }, () => {}]);

LoggedInContextについては特に解説の必要がないと思います。単純にログイン状態をbooleanで型指定して、デフォルト値をfalseでセットしています。
ユーザー情報を管理するAuthInfoContext3. どのコンポーネントからでもログイン・ログアウトが可能の条件を満たすためにほんの少しだけ複雑な作りになっています。ここでcreateContext<>()の型引数で指定している型はuseState<>()の返り値の型です。こうすることで、タプルの2つ目の関数を介してどのコンポーネントからでも自由に認証情報をセットできますね。

本当はReturnType<typeof useState<AuthInfo>>と書きたかったのですがtypeofがgenericに対応していないため直接指定することになりました(泣)。

今作成したContextを使うためには、値を使用したいコンポーネントより外側でContextのProviderを呼び出してあげればOKです。もちろん直接ルートコンポーネントあたりに直で書いてもOKです。

今回は、可読性・メンテナンス性を考えてさらにラッパーコンポーネントを作成することにします。

Context Providerのラッパーコンポーネントを作成

先程Contextを作成したファイルに追加でラッパー関数を作成します。

AuthContextProvider.tsx
[...]


export const AuthContextProvider: React.FC<{}> = (props) => {
  // stateの定義
  const [loggedIn, setLoggedIn] = useState<boolean>(false);
  const [authInfo, setAuthInfo] = useState<AuthInfo>({ userId: "" });

  // authInfoのバリデーション
  useEffect(() => {
    // authInfoに正しく値がセットされているかどうかをチェック
    if (authInfo?.userId) {
      setLoggedIn(true);
    } else {
      setLoggedIn(false);
    }
  }, [authInfo]);
  return (
    <LoggedInContext.Provider value={loggedIn}>
      <AuthInfoContext.Provider value={[authInfo, setAuthInfo]}>
        {props.children}
      </AuthInfoContext.Provider>
    </LoggedInContext.Provider>
  );
};

このコンポーネントは子要素のみを受け取るコンポーネントとして設計しています。子要素を受け取るコンポーネントの場合、コンポーネントの型として直接 React.FC<P>の型を指定します。今回はpropsは受け取ら無いのでPには空の型を指定しています。

次にコンポーネントの中の説明に移ります。useState<T>()でさきほどのContextで指定した値をstateとして定義しています。そのうちのauthInfouseEffect()によって変更が監視され、正しい値が入った場合のみloggedIntrueがセットされます。

TSX部分ではこのコンポーネントの子要素がすべてContextの値を取得できる様に{props.children}をContextのProviderで囲んでいます。

自動ログイン機能

次に2.以前にログインしたことがあれば自動で認証情報を取得を考えてみましょう。

自動ログイン機能を実装する最も簡単な方法はログイン時にブラウザのローカルストレージに情報を保持しておいて、次にアクセスした際にそこから情報を取り出す方法です。

この機能を関数として実装してみましょう。

AuthContextProvider.tsx
[...]

/**
 * デフォルトのAuthInfoを取得
 * ローカルストレージから取得できた場合はその値をパース
 * 取得できない場合は空の情報を返す
 * @returns
 */
function getDefaultAuthInfo(): AuthInfo {
  const defaultAuthInfo = window.localStorage.getItem("authInfo");
  if (defaultAuthInfo) {
    return JSON.parse(defaultAuthInfo) as AuthInfo;
  } else {
    return { userId: "" };
  }
}

/**
 * 認証情報をローカルストレージに追加
 * @param authInfo
 */
function setAutoInfoToLocalStorage(authInfo: AuthInfo): void {
  const authInfoStringfy = JSON.stringify(authInfo);
  window.localStorage.setItem("authInfo", authInfoStringfy);
}


[...]

作成した関数を先程作成したProviderコンポーネント上で呼び出します。

AuthContextProvider.tsx
/**
 * コンテキストのProvider
 */
export const AuthContextProvider: React.FC<{}> = (props) => {
  // stateの定義
  const [loggedIn, setLoggedIn] = useState<boolean>(false);
  const [authInfo, setAuthInfo] = useState<AuthInfo>(getDefaultAuthInfo());

  // authInfoのバリデーション
  useEffect(() => {
    // authInfoに正しく値がセットされているかどうかをチェック
    if (authInfo?.userId) {
      setAutoInfoToLocalStorage(authInfo);
      setLoggedIn(true);
    } else {
      setLoggedIn(false);
    }
  }, [authInfo]);
  return (
    <LoggedInContext.Provider value={loggedIn}>
      <AuthInfoContext.Provider value={[authInfo, setAuthInfo]}>
        {props.children}
      </AuthInfoContext.Provider>
    </LoggedInContext.Provider>
  );
};

これでページ読み込み時にはブラウザのローカルストレージからデータの読み込みを試行して、ログイン成功時にはローカルストレージにデータを書き込むようになりました!

使ってみよう!

実際に使って見ます。
まずは認証機能、情報を使用するコンポーネントをProviderラッパーコンポーネントで包みます。今回は一番ルートのindex.tsxで呼び出して見ましょう。

index.tsx
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { AuthContextProvider } from "./AuthContextProvider";

ReactDOM.render(
  <React.StrictMode>
    <AuthContextProvider>
      <App />
    </AuthContextProvider>
  </React.StrictMode>,
  document.getElementById("root")
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

次に内側のコンポーネントでContextを呼び出します。

App.tsx
import React, { useContext } from "react";
import { LoggedInContext, AuthInfoContext } from "./AuthContextProvider";

function App() {
  const isLoggedIn = useContext(LoggedInContext);
  const [authInfo, setAuthInfo] = useContext(AuthInfoContext);
  return (
    <div>
      {isLoggedIn ? `ID: ${authInfo?.userId}` : "ログインされていません"}
      <button
        onClick={() => {
          setAuthInfo({ userId: "abcdefg123455" });
        }}
      >
        ログイン
      </button>
      <button
        onClick={() => {
          setAuthInfo({ userId: "" });
        }}
      >
        ログアウト
      </button>
    </div>
  );
}

export default App;

この状態でログイン、ログアウトボタンをクリックしたりページ読み込みをしたりしてみてください。うまく認証情報が扱えていることが確認できると思います!

これはあくまで非常に簡単にした使用例であることに注意してください。実際にはログインフォームをサーバーにPOSTして、Responseとして受け取ったUserIdやセッションIDをセットします。

もっと深い子要素のコンポーネントを作成してもuseContextで作成したContextを呼び出せば認証情報・ログイン状態を自由に取得できます!確認してみましょう!!

このエントリーをはてなブックマークに追加


関連記事

記事へのコメント