個人開発勉強①のコード内容理解
今回は前回の記事でClaudeに出力してもらったコードを理解することに努めようと思う。
index.htmlの理解
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Firebase Auth</title>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300&display=swap" rel="stylesheet">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<!-- タブ切り替え用ナビゲーション -->
<div class="tabs">
<button id="login-tab" class="tab-btn active">ログイン</button>
<button id="register-tab" class="tab-btn">新規登録</button>
</div>
<!-- ログインフォーム -->
<div id="login-form" class="auth-form">
<h2>ログイン</h2>
<input type="email" id="login-email" placeholder="メールアドレス"><br>
<input type="password" id="login-password" placeholder="パスワード"><br>
<button id="login-btn">ログイン</button>
<p id="login-error" class="error-message"></p>
</div>
<!-- 登録フォーム -->
<div id="register-form" class="auth-form" style="display: none;">
<h2>新規登録</h2>
<input type="email" id="register-email" placeholder="メールアドレス"><br>
<input type="password" id="register-password" placeholder="パスワード"><br>
<input type="password" id="confirm-password" placeholder="パスワード(確認)"><br>
<button id="register-btn">登録</button>
<p id="register-error" class="error-message"></p>
</div>
</div>
<!-- ログイン後の画面 -->
<div id="message" style="display: none;">
<h1>What are you going to do today?</h1>
<button id="logout-btn">ログアウト</button>
</div>
<!-- Firebase SDKs -->
<script src="https://www.gstatic.com/firebasejs/9.6.1/firebase-app-compat.js"></script>
<script src="https://www.gstatic.com/firebasejs/9.6.1/firebase-auth-compat.js"></script>
<script src="app.js"></script>
</body>
</html>
-
<!DOCTYPE html>
- HTML5で文書が書かれることを宣言。どうして直接的にHTML5と明記しないの?
- 以前のバージョンではDOCTYPE宣言が複雑だった
- HTML5ではウェブ開発をもっとシンプルし、互換性を高めたい
- HTML5では「Living Standard」を重視し、最新のHTML標準をブラウザに要求することで、バージョンアップ時に無駄な変更を無くしたい
- HTML5で文書が書かれることを宣言。どうして直接的にHTML5と明記しないの?
-
head
セクション-
<meta charset="UTF-8">
- 文字エンコーディングを
UTF-8
に設定- 文字エンコーディングは文字をコンピュータが理解できるように数値へ変換するルールを定めたもの
- 昔は地域ごとに異なるエンコーディングが使用されていた
- 複数の言語に対応できるエンコード方式が共有れていたない状況だった
-UTF-8:ウェブ標準として広くサポートされ、互換性が高い
- 複数の言語に対応できるエンコード方式が共有れていたない状況だった
- 文字エンコーディングを
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
- モバイルデバイス対応のための設定
- width=device-width : ページの幅をデバイスの画面幅に合わせる
- initial-scale=1.0 : ページの初期ズームレベルを等倍(1.0)に設定
- <meta>タグ
- メタデータを定義する
- 単独タグで一行で完結する(</meta>はない)
- viewport : name属性で指定できる「画面のなかでページがどう見えるか」の設定宣言みたいなもの
-
<title>Firebase Auth</title>
: ページのタイトル。ブラウザのタブやブックマークに「Firebase Auth」と表示される -
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300&display=swap" rel="stylesheet">
- Google Fontsから「Roboto」フォント(ウェイト300)を読み込む。このフォントはCSSで使用可能
-
<link rel="stylesheet" href="styles.css">
- 外部CSSファイル「styles.css」を読み込む。ここで詳細な文字デザインや配色などがきまる
-
rel属性
: relationshipの略。指定リンクがどのような目的で使用されているかを明示する -
href属性
: Hypertext Referenceの略。ここでは相対パス(/index.html , /styles.css 同じディレクトリにある意)。絶対パスを示すのはURLなど外部リンクがほとんど。Google Fontsなど。
-
-
body
セクション-
div
タグ : divisionの略。ページ内の要素を整理するための「コンテナ(箱)」のような役割。- <div>には必ず終了タグ(</div>)を付けて、どこまでがその範囲かを明確にする
-
<h2>ログイン</h2>
においてh1
が使われていない理由- <h1>は通常、ページ全体の主題やタイトルに使用
- SEO的な理由から、検索エンジンにページ内容の重要性を明確に判断してもらうため
-
: breakの略。HTML内でテキストや要素を改行するためのタグ。単独タグのため、終了タグ(</br>)は不要 - <p></p> : paragraphの略。上下にマージンが付く。ここでは、あとでJavaScriptで詳細を記述できるid, classが付けてある。
-
- Firebase SDKs
- Firebaseの基本機能を読み込む。
- 認証機能モジュールを読み込む
- <script src="app.js"></script>
- 外部JavaScriptファイル「app.js」を読み込みます。このファイルには、Firebaseの初期化や認証処理の実装が含まれている。上記で取り込んだモジュールをさらにカスタマイズして利用するためのもの。
styles.cssの理解
body {
font-family: 'Roboto', sans-serif;
background-color: #f5f5f5;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.container {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
width: 350px;
padding: 20px;
}
.tabs {
display: flex;
margin-bottom: 20px;
border-bottom: 1px solid #e0e0e0;
}
.tab-btn {
flex: 1;
background: none;
border: none;
padding: 10px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s ease;
}
.tab-btn.active {
color: #4285F4;
border-bottom: 2px solid #4285F4;
}
.auth-form {
text-align: center;
}
.auth-form h2 {
margin-top: 0;
color: #333;
}
input {
width: 100%;
padding: 12px;
margin: 8px 0;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
font-size: 14px;
}
button {
background-color: #4285F4;
color: white;
border: none;
border-radius: 4px;
padding: 12px 20px;
margin: 10px 0;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
}
button:hover {
background-color: #3367D6;
}
#message {
display: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
background-color: white;
padding: 40px;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}
#message h1 {
font-size: 36px;
font-weight: 300;
color: #333;
margin-bottom: 30px;
}
#logout-btn {
background-color: #f44336;
}
#logout-btn:hover {
background-color: #d32f2f;
}
.error-message {
color: #f44336;
font-size: 14px;
margin: 8px 0;
min-height: 20px;
}
CSSは基本的にHTMLのセレクタ(タグやクラスなど)に対して部分的にスタイルを指定することができる。
細かいデザインなどは今のところ飛ばす(LLMに今は多くを頼ろう。)として、基本的な構造だけ抑えておこう。
-
要素セレクタ
- body
- ページ全体のスタイルに適応
- input
- 入力欄のスタイル(今回の場合はメールアドレスやパスワードを入力する欄でのスタイルということ)
- body
-
クラスセレクタ(タグ内でclass=""で定義される部分に適応)
- .containder, .tab-tbn, のように「.」が必要になる。
.container {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.tab-btn {
padding: 10px 20px;
border: none;
}
<div class="container">
<!-- コンテンツ -->
</div>
<button class="tab-btn">ログイン</button>
- IDセレクタ
- HTMLのid属性で指定された特定の要素にスタイルを適用。IDセレクタは
#
で始まる。
- HTMLのid属性で指定された特定の要素にスタイルを適用。IDセレクタは
#message {
color: #333;
}
#logout-btn {
background-color: #f44336;
}
<div id="message">メッセージ</div>
<button id="logout-btn">ログアウト</button>
- 子孫セレクタ
- 複数のセレクタをスペースで組み合わせ、特定の親要素内の子要素にスタイルを適用。すなわち、親要素のなかに子要素が複数あるときに、子要素を指定して特定の場所(ここではh2)にスタイルを適用したいということ。
.auth-form h2 {
font-size: 24px;
margin-bottom: 20px;
}
<div class="auth-form">
<h2>ログイン</h2>
<h3>aaaaaaa</h3>
</div>
今回はこのあたりまで抑えておこう。
app.jsの理解
// Firebase設定(実際の値に置き換えてください)
const firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_AUTH_DOMAIN",
projectId: "YOUR_PROJECT_ID",
storageBucket: "YOUR_STORAGE_BUCKET",
messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
appId: "YOUR_APP_ID"
};
// Firebase初期化
firebase.initializeApp(firebaseConfig);
const auth = firebase.auth();
// 要素の取得
// タブ要素
const loginTab = document.getElementById('login-tab');
const registerTab = document.getElementById('register-tab');
// フォーム要素
const loginForm = document.getElementById('login-form');
const registerForm = document.getElementById('register-form');
// ログインフォーム
const loginEmail = document.getElementById('login-email');
const loginPassword = document.getElementById('login-password');
const loginBtn = document.getElementById('login-btn');
const loginError = document.getElementById('login-error');
// 登録フォーム
const registerEmail = document.getElementById('register-email');
const registerPassword = document.getElementById('register-password');
const confirmPassword = document.getElementById('confirm-password');
const registerBtn = document.getElementById('register-btn');
const registerError = document.getElementById('register-error');
// その他UI要素
const messageDiv = document.getElementById('message');
const logoutBtn = document.getElementById('logout-btn');
// タブ切り替え機能
loginTab.addEventListener('click', () => {
loginTab.classList.add('active');
registerTab.classList.remove('active');
loginForm.style.display = 'block';
registerForm.style.display = 'none';
});
registerTab.addEventListener('click', () => {
registerTab.classList.add('active');
loginTab.classList.remove('active');
registerForm.style.display = 'block';
loginForm.style.display = 'none';
});
// ユーザー登録処理
registerBtn.addEventListener('click', () => {
const email = registerEmail.value;
const password = registerPassword.value;
const passwordConfirm = confirmPassword.value;
// 入力値の検証
if (!email || !password || !passwordConfirm) {
registerError.textContent = 'すべての項目を入力してください';
return;
}
if (password !== passwordConfirm) {
registerError.textContent = 'パスワードが一致しません';
return;
}
// エラーメッセージをクリア
registerError.textContent = '';
// Firebaseで新規ユーザー登録
auth.createUserWithEmailAndPassword(email, password)
.then((userCredential) => {
console.log('ユーザー登録成功:', userCredential.user);
// 登録成功後、メッセージ画面に遷移する(onAuthStateChangedで処理)
})
.catch((error) => {
console.error('登録エラー:', error);
// エラーメッセージの表示
switch(error.code) {
case 'auth/email-already-in-use':
registerError.textContent = 'このメールアドレスは既に使用されています';
break;
case 'auth/invalid-email':
registerError.textContent = '無効なメールアドレスです';
break;
case 'auth/weak-password':
registerError.textContent = 'パスワードは6文字以上必要です';
break;
default:
registerError.textContent = `エラーが発生しました: ${error.message}`;
}
});
});
// ログイン処理
loginBtn.addEventListener('click', () => {
const email = loginEmail.value;
const password = loginPassword.value;
// 入力値の検証
if (!email || !password) {
loginError.textContent = 'メールアドレスとパスワードを入力してください';
return;
}
// エラーメッセージをクリア
loginError.textContent = '';
// Firebaseでログイン
auth.signInWithEmailAndPassword(email, password)
.then((userCredential) => {
console.log('ログイン成功:', userCredential.user);
// ログイン成功後、メッセージ画面に遷移する(onAuthStateChangedで処理)
})
.catch((error) => {
console.error('ログインエラー:', error);
// エラーメッセージの表示
switch(error.code) {
case 'auth/invalid-email':
loginError.textContent = '無効なメールアドレスです';
break;
case 'auth/user-disabled':
loginError.textContent = 'このアカウントは無効になっています';
break;
case 'auth/user-not-found':
case 'auth/wrong-password':
loginError.textContent = 'メールアドレスまたはパスワードが正しくありません';
break;
default:
loginError.textContent = `エラーが発生しました: ${error.message}`;
}
});
});
// ログアウト処理
logoutBtn.addEventListener('click', () => {
auth.signOut()
.then(() => {
console.log('ログアウト成功');
// ログアウト後の処理はonAuthStateChangedで行われる
})
.catch((error) => {
console.error('ログアウトエラー:', error);
});
});
// 認証状態の監視
auth.onAuthStateChanged((user) => {
if (user) {
// ログイン済み
console.log('ログイン状態:', user);
// フォームを非表示にし、メッセージを表示
document.querySelector('.container').style.display = 'none';
messageDiv.style.display = 'block';
// フォームをリセット
loginForm.reset();
registerForm.reset();
} else {
// ログアウト状態
console.log('ログアウト状態');
// メッセージを非表示にし、フォームを表示
document.querySelector('.container').style.display = 'block';
messageDiv.style.display = 'none';
// 初期表示ではログインタブを表示
loginTab.click();
}
});
- const について
-
定数(constant)の定義。一度値が設定されるとあとから変更できない変数のこと。再代入ができない。
-
ブロックスコープをもつ
- {}でのみ有効で、外からは値を参照できない
-
const変数は型だけでは成立しない。要するに値を具体的に設定(初期化)する必要あり。
-
const x = 10;
x = 20; // エラー: Assignment to constant variable.
-
スコープ(scope)とは?
- 変数が有効で参照可能な範囲のこと
- グローバルスコープ: プログラム全体でアクセス可能な範囲
- ローカルスコープ: 特定の関数やブロック内でしかアクセスできない範囲
- 関数スコープ: 関数内で定義された変数の範囲(varでよく使われる)
- ブロックスコープ: ブロック({})内で定義された変数の範囲(constやletで使われる)
- 変数が有効で参照可能な範囲のこと
-
HTMLとJavaScriptの命名規則を把握
- HTMLの命名規則
- idやclass属性の名前にハイフン(-)を使うのが一般的。これをケバブケースと呼ぶ。
- HTMLは小文字が推奨されており、属性値に大文字を使うことはあまりない。非標準的。
- CSSにおいても、そのままHTMLから名前を引っ張ってくるので一貫性は保たれている。
- JavaScriptの命名規則
- キャメルケース(単語の先頭を大文字にしてつなげる)が慣習的に採用されている。
- ただし、見た感じだと最初の一文字目は小文字になっている
- 推奨、非推奨の問題以前にJavaScriptでは変数名にハイフンを使用すると構文エラーとなる
- JavaScriptでは関数も作れるので、ハイフン(-)は減算演算子と解釈されることが理由の一つとして挙げられる。
- アンダースコア(_)を使えないことはないが、キャメルケースが主流となっている。
- HTMLの命名規則
-
activeクラスについて
- JavaScriptで動的に扱えるクラスとして機能している。初期状態では #login-tab にだけ付いているが、クリックに応じて #register-tab に移動する。
- このとき実際にHTMLファイルが書き換えられているわけではなく、ブラウザ内で動作している DOM(Document Object Model) が動的に変更されている。
- ブラウザのデベロッパーツール(F12キー)を開いて「要素(Elements)」タブを見ると、現在のDOM状態が確認できる。
- HTMLで最初にactiveクラスを明示しておかずとも動作はするが、明示しておくのが一般的で推奨される場合が多い。ージの初期表示時にどのタブがアクティブかをユーザーに分かりやすく示し、かつJavaScriptとCSSの動作を一貫させるため。
loginTab.addEventListener('click', () => {
loginTab.classList.add('active');
registerTab.classList.remove('active');
loginForm.style.display = 'block';
registerForm.style.display = 'none';
});
registerTab.addEventListener('click', () => {
registerTab.classList.add('active');
loginTab.classList.remove('active');
registerForm.style.display = 'block';
loginForm.style.display = 'none';
});
<!-- タブ切り替え用ナビゲーション -->
<div class="tabs">
<button id="login-tab" class="tab-btn active">ログイン</button>
<button id="register-tab" class="tab-btn">新規登録</button>
</div>
- 非同期処理について
- ログイン機能やログアウト機能、新規ユーザー登録機能のコードを見ていると、成功時が先に書かれて、失敗時のコードが後に書かれているのに気が付いた。感覚的には順番に実行がなされるイメージだったが、調べてみると非同期処理というものが関係しているらしい。
- .then() と .catch() は「順番に実行される」のではなく、結果に応じてどちらか一方だけが実行されることになる。
- メソッドが非同期でサーバーにリクエストを送り、結果をPromiseとして返して、それが解決(resolved)」されると、.then()が実行されて、「拒否(rejected)」されると、.catch()が実行される。
// Firebaseでログイン
auth.signInWithEmailAndPassword(email, password)
.then((userCredential) => {
console.log('ログイン成功:', userCredential.user);
// ログイン成功後、メッセージ画面に遷移する(onAuthStateChangedで処理)
})
.catch((error) => {
console.error('ログインエラー:', error);
// エラーメッセージの表示
switch(error.code) {
case 'auth/invalid-email':
loginError.textContent = '無効なメールアドレスです';
break;
case 'auth/user-disabled':
loginError.textContent = 'このアカウントは無効になっています';
break;
case 'auth/user-not-found':
case 'auth/wrong-password':
loginError.textContent = 'メールアドレスまたはパスワードが正しくありません';
break;
default:
loginError.textContent = `エラーが発生しました: ${error.message}`;
}
}
});
-
messageDiv.style.display = 'block'; について
- messageDivはJavaScriptでHTMLの要素を動的に変更できるように設定したオブジェクト
- styleはCSSスタイルを操作するためのオブジェクトで文法的な約束事と捉えるとよいかも。
- displayも同様の理解。
-
今回のWebアプリはシングルページだ。
// 認証状態の監視
auth.onAuthStateChanged((user) => {
if (user) {
// ログイン済み
console.log('ログイン状態:', user);
// フォームを非表示にし、メッセージを表示
document.querySelector('.container').style.display = 'none';
messageDiv.style.display = 'block';
// フォームをリセット
loginForm.reset();
registerForm.reset();
} else {
// ログアウト状態
console.log('ログアウト状態');
// メッセージを非表示にし、フォームを表示
document.querySelector('.container').style.display = 'block';
messageDiv.style.display = 'none';
// 初期表示ではログインタブを表示
loginTab.click();
}
});
このコードを見て気が付いた。動的に表示内容を消したり表したりしているだけで、全然ログインした感じがしない。
こういう疑問は、シングルページアプリケーション(SPA)とマルチページアプリケーション(MPA)の設計思想に繋がっているのかもしれない。
-
シングルページアプリケーション(SPA)
- 1つのHTMLページ内でJavaScript(React、Vue、Angularなど)を使って、表示内容を動的に切り替える。URLはルーティングライブラリ(React Routerなど)で管理され、見た目は変わるがページ全体のリロードはない。
-
マルチページアプリケーション(MPA)
- 各ページが独立したHTMLファイル(/login.html、/dashboard.html)で、サーバー側で新しいページをリクエストするごとにURLが変わり、ページ全体がリロードされる。
今後について再検討
文法的なことをコードを読みながら理解してきた。そのなかでSPAやMPAなどのコードを読まないと気が付かなかった概念まで理解するに至った。やはり、コードをじっくりと読む行為は重要だ。しかし、これからは一つ一つを詳細に記事のなかでメモをしていくやり方はベストではないかもしれない。むしろ、実査にコーディングして、実際にエラーが出るところを確認したりすることのほうが重要になってくる。記事のなかで言及すべきことの選定をもう少しだけ取り組みの中で動的に体得していきたい。
またこれは鉄則にしたいが、逐一実装の手順を記事化する必要性をもうすでに感じていないので、それはやめること。
- 実装した機能を公開すること
- そのなかで得た学びを端的にまとめること
この二つだけで記事を構成すべきだと感じている。
以下は今後、取り組みたいこと&実装したい機能を書いていく。
Discussion