【もりけん塾】JS課題35 – Vanilla JS/ログイン状態別でコンテンツの出しわけ –

JavaScript

練習のためにmockAPIを使用したログイン実装に変更し、かつ、ログイン済みか否かで見れるコンテンツを変えられるようにしました !

こんにちは。はるです。

現在、所属している「もりけん塾」でJavaScriptのハンズオン課題 に取り組み、レビューをいただいています。

今日は、課題35 についてアウトプットをしていきます。
(前回までのアウトプットは、こちらです。)

実装する過程で学んだことを、学習ノートとして記録していきます。

認識の間違えている箇所がありましたら、お問い合わせからご指摘いただけるとうれしいです。

JavaScript課題35の仕様

今回は仕様の変更があり、元々ローカルストレージで実装していたログイン機能を、mockAPIからのデータを取得・照合する方法に変更しました。

    【旧仕様】

    • ログイン済みのユーザーが見れるコンテンツとそうではないユーザーが見れるコンテンツです
    • APIでユーザーに関するmeというエンドポイントのAPIを作り、 そこにはisAuthorizationのようなフィールドがあり、
    • trueだったらログイン済みなページ ログイン済みなページでは課題のyahooページのコンテンツを表示して何かログイン済みユーザー特有のコンテンツに変更してください
    • この場合ログインボタンの文言は「マイページ」となります
    • falseなら 通常の作成済みのyahooを模したコンテンツが表示され ログインボタンが置いてあり、ページに遷移します

    【変更点】

    1️⃣ isAuthorizationは廃止。
    2️⃣ ログインページでログイン時に「mockAPIのユーザー情報」と照合して、合っていればその情報の中にある”userId”をtokenとしてローカルストレージに保存する。
    3️⃣ index.htmlは非ログインユーザーが見るページで、アクセス時にローカルストレージにtokenがあれば、ログインユーザー向けページ logged-in.htmlに遷移する

    ※会員登録ページの流れは変更なし。フォームに入力された情報は登録時にlocalStorgeに保存される。→ その情報でログインするという仕様がなくなる。mockAPIの情報と照合するように変更された。

    (もりけんさんのJavaScriptハンズオン課題より)

    JavaScript課題35の学習記録

    ログイン済みユーザー向けのページと、非ログインユーザー向けのページを作成

    index.html

    ユーザーが最初にアクセスするページをindex.htmlとして、こちらは非ログインユーザー向けページにしました。

    logged-in.html

    ログインしているユーザーが見れるページとしてlogged-in.htmlを新規作成しました。

    mockAPIを使用してログイン機能を実装する

    【Before】

    ① 会員登録で入力したIDやパスワードをローカルストレージに保存
    ② ログイン画面で入力した値と、ローカルストレージの値を照合して正しければ、index.htmlに遷移。

    【After】

    ① 会員登録の挙動は変わらず
    ② ログイン時にmockAPIのデータを取得して、入力値と照合して正しければ、logged-in.htmlに遷移。

    mockAPI で模擬APIの作成

    簡単に模擬APIを作成できるサービスです。

    簡易APIを作成して、実際にデータをfetch(GETメソッドやPOSTメソッドも実際に使用できる)することができます。key, valueに関する設定をすれば、簡単にデモデータを用意してもらえるのでとても便利でした。

    丁寧なドキュメントもあり、とても理解しやすいです。

    今回は、usersという名前の resoueceを作成、API endpointを生成しました。

    // mockAPIで作成してもらったユーザーに関するデモデータ
    
    [
     {
      "name": "Ruecker",
      "email": "Dakota.Hartmann20@example.com",
      "password": "NaHtsFAqPT1_OwK",
      "userId": "1bc3e7fafee51ea4edd9fbca",
      "id": "1"
     },
     {
      "name": "Hamill",
      "email": "Hank_Kris@example.org",
      "password": "CbZac0M6H57IV97",
      "userId": "f8daa6cbbb6afc18acfd21a3",
      "id": "2"
     },
     {
      "name": "Botsford",
      "email": "Tyreek3@example.net",
      "password": "NgHK41c1rxiBu9g",
      "userId": "4d980f91eecaccdb6dab066c",
      "id": "3"
     },
     {
      "name": "Dicki",
      "email": "Lane20@example.net",
      "password": "cW3SfU2Lkfb1Yfm",
      "userId": "f612ba1c0c5ccf45c0f57e97",
      "id": "4"
     },
     {
      "name": "Dicki",
      "email": "Demario_Hoppe16@example.org",
      "password": "fLDg1A3Tj2qeNsH",
      "userId": "259c2dfaca8f1eedcc5dbfb0",
      "id": "5"
     }
    ]
    passwordに関する情報も入っていますが、本番ではAPIでパスワードのやり取りをすることはなく、課題の練習上の仕様として使用しています。

    データをfetchする

    mockAPIでusersというresourceを作成したので、https://652dc444f9afa8ef4b27cca3.mockapi.io/users というエンドポイントからfetchします。
    const url = "https://652dc444f9afa8ef4b27cca3.mockapi.io/users"; //mockAPI
    
    // ユーザーに知らせたいテキストを出力
    const displayInfo = (target, error) => {
     const p = document.createElement("p");
     p.textContent = error;
     target.appendChild(p);
    };
    
    // エラーの情報を出力
    const displayErrorStatus = (target, response) => {
     const p = document.createElement("p");
     p.textContent = `${response.status}:${response.statusText}`;
     target.appendChild(p);
    };
    
    // APIからデータをfetchする汎用的な関数
    const fetchData = async (api) => {
     try {
      const response = await fetch(api);
    
      if (response.ok) {
       return await response.json();
      } else {
       console.error(`${response.status}:${response.statusText}`);
       displayErrorStatus(errorArea, response);
      }
     } catch (error) {
      displayInfo(errorArea, error);
     }
    };
    
    // 登録ユーザーデータを取得するための関数。ここでAPIのurlを引数で渡す。
    const fetchRegisteredData = async () => {
     const data = await fetchData(url);
     if (!data) return;
    
     if (data.length) {
      return data;
     }
    };

    これで、先ほど記載したjsonデータが取得できます。

    fetchしたデータと、ログイン画面で入力したinputの値を照合する

    const userIdOfInput = document.querySelector(".js-form-userid");
    const passwordOfInput = document.querySelector(".js-form-password");
    
    const checkToRegistered = async () => {
     const registeredUsers = await fetchRegisteredData();
     const user = registeredUsers.find(user => user.name === userIdOfInput.value || user.email === userIdOfInput.value);
    
     if (user && user.password === passwordOfInput.value) {
      return { token: user.userId, ok: true, code: 200 };
     } else {
      throw new Error({ ok: false, code: 401 });
     }
    }

    APIから登録ユーザーのデータを取得します。

    const registeredUsers = await fetchRegisteredData();
    

    inputに入力したユーザーID(名前かメールアドレス)が、APIから取得したjsonデータに合致するものがあるか調べます。

    const user = registeredUsers.find(user => user.name === userIdOfInput.value || user.email === userIdOfInput.value);
    
    合致するものがあれば、そのユーザーの登録情報が取得できます。
    例えばここでAdamさんの情報が取得できたとします。=変数userに下記のデータが入る
    {
      "name": "Adams",
      "email": "Granville83@example.org",
      "password": "3W7j0bv11xf4odx",
      "userId": "be29fa5ed5ca3ef7f2633b09",
      "id": "4"
    },
    
    ・変数userの情報が存在し、入力したパスワードとuser.passwordの情報が一致すれば、{ token: user.userId, ok: true, code: 200 }というオブジェクトを返します。
    ・一致しなければ、throw new Errorで{ ok: false, code: 401 }というエラーオブジェクトを投げます。
    本番ではAPIでパスワードのやり取りをすることはなく、課題の練習上の仕様として使用しています。
    if (user && user.password === passwordOfInput.value) {
      return { token: user.userId, ok: true, code: 200 };
    } else {
      throw new Error({ ok: false, code: 401 });
    }
    

    throw({ok: false, code: 401})と書いていたのですが、レビューで throw new Error()をご提案いただき、違いを調べました。

    ・throw → 例外を投げる, あらゆる任意の式を投げることができる
    ・throw new Error() → Errorオブジェクトの新しいインスタンスを投げる

    Errorオブジェクトは、単にエラーが発生したことを示すだけでなくエラーの内容をより詳細に記述するためにも使用されるなど、throw new Errorの方が明確にエラーを投げていることを示せるのかなと思いました。 参考: MDN

    ログイン画面で送信ボタンを押した時

    ボタンを押すと、login関数が実行されます。
    submitButton.addEventListener("click", login);
    
    const login = async () => {
     let result;
     try {
      result = await checkToRegistered();
     } catch (error) {
      console.error('Login failed:', error);
      window.location.href = "./notautherize.html";
      return;
     }
     localStorage.setItem("token", result.token);
     window.location.href = "./logged-in.html";
    }

    login関数では、checkToRegistered関数をtryします。

    ・エラーが投げられた場合は、consoleにエラーを出しつつ、権限がありませんページに遷移します。
    ・tryが成功した場合は、ローカルストレージにAPIから取得したデータにあるuserIdをtokenとして保存して、ログインユーザー向けのページへ遷移します。
    ここではローカルストレージの危険性を理解した上で、課題上の練習のために使用しています。また、tokenとしてAPIから取得したuserIdを保存するという流れも本来はしないですが、練習上行っています。

    login関数では、try, catchの書き方をレビューで教わりました。

    元々書いていたコード

    const tryToLogin = async () => {
     try {
      const result = await checkToRegistered();
      localStorage.setItem("token", result.token);
      window.location.href = "./logged-in.html";
     } catch (rejectObj) {
      console.error('Login failed:', rejectObj);
      window.location.href = "./notautherize.html";
     }
    }

    tryの中に、awaitのコード以外だけでなく、成功した時に処理も一緒に書いていました。レビューで、tryには非同期処理やエラーが起こりうる危ういコードを囲むのが一般的と教わりました。

    今回のコードだと、tryで非同期処理のawait checkToregistered();のみを囲み、catchでreturnした後に成功後の処理を書くように修正しました。
    try, catchの構文の意味に沿っているし、何に対してtryしているのかが明確で、とてもわかりやすいコードになったと思いました !

    ログインユーザーと非ログインユーザーで表示するページを出し分ける

    出しわけには、練習で使用しているローカルストレージの値を用いました。

    まず、最初にユーザーがアクセスするページは index.html(非ログインユーザー向けページ)です。

    // index.html 
    <head>
     <script>if (localStorage.getItem("token")) window.location.href = "./logged-in.html";</script>
    </head>

    アクセスした時にローカルストレージのtokenをチェックし、tokenがある(=ログイン済み)ならばログインユーザー向けのページに遷移します。

    logged-in.html(ログイン)でも、アクセス時にtokenの有無をチェックして、ない場合は非ログインユーザー向けのページに遷移するようにしています。

    <script>if (!localStorage.getItem("token")) window.location.href = "./index.html";</script>
    

     

    コード

    lesson35のリンクから実際の挙動を確認できます。

    ログインページにて、jsonデータのユーザーどれかの情報を入力すると、ログイン済みユーザー向けのページ(logged-in.html)に遷移することが確認できます。


    //

    学習に使用している本は、JavaScript本格入門・独習JavaScriptです。

      

    あとがき

    もりけんさん(@terrace_tec)お忙しい中レビューいただきありがとうございました!

    仕様の変更があり、実装も手探りで進めていた部分が大きく、粒度を分けたPRにできなかったのは反省です。でも、やりたいことを実装することができ、try, catchの部分でレビューをいただけて大変勉強になりました !

    今日は以上です。

    //

    【もりけん塾で勉強しています】

    もりけんさん(@terrace_tec)のHPはこちら