بناء صفحة تسجيل الدخول بـ React: من النموذج إلى الصفحة المحمية
صفحة تسجيل الدخول جزء يكاد يكون في كل موقع. تبني هذه المقالة صفحة كهذه بـ React خطوة بخطوة: بنية النموذج، والحقول المتحكَّم بها، والتحقق من المدخلات، ومحاكاة تسجيل الدخول، والقفل بعد محاولات فاشلة متكررة، ومشاركة حالة المستخدم عبر Context، وصفحة محمية تُخفي المحتوى عمّن لم يسجل دخوله. كل مثال شُغِّل فعليًا، والمخرجات المعروضة هي المخرجات الفعلية.
ما الذي تحتاجه
تحتاج إلى Node.js 18 أو أحدث وأساسيات React. تستخدم الأمثلة React 18.3.1 وتعمل بمشغّل الاختبار الخاص بهذه المقالة الذي ينقر الأزرار في متصفح محاكى عبر jsdom. تنبيه مهم: التحقق من كلمة المرور داخل المتصفح للتعلّم فقط. النظام الحقيقي يجب أن يتحقق من كلمة المرور على الخادم، ويخزنها مُجزّأة، ويستخدم HTTPS.
رسوم توضيحية
المثال 1: بنية نموذج تسجيل الدخول
نبدأ بنموذج فيه حقل البريد وحقل كلمة المرور وزر الإرسال. تُظهر renderToStaticMarkup شيفرة HTML التي ينتجها React.
import React from "react";
export function App() {
return (
<form>
<h2>เข้าสู่ระบบ</h2>
<label>อีเมล<input type="email" name="email" /></label>
<label>รหัสผ่าน<input type="password" name="password" /></label>
<button type="submit">เข้าสู่ระบบ</button>
</form>
);
}
المخرجات الفعلية
<form><h2>เข้าสู่ระบบ</h2><label>อีเมล<input type="email" name="email"/></label><label>รหัสผ่าน<input type="password" name="password"/></label><button type="submit">เข้าสู่ระบบ</button></form>
المثال 2: إظهار كلمة المرور وإخفاؤها
نحفظ حالة الإظهار في state ونبدّل نوع الحقل بين password وtext عند النقر على الزر.
1. انقر زر إظهار كلمة المرور مرة واحدة من إيقاف إلى تشغيل
import React, { useState } from "react";
export function App() {
const [show, setShow] = useState(false);
return (
<div>
<input type={show ? "text" : "password"} value="secret1" readOnly />
<button id="toggle" onClick={() => setShow(!show)}>แสดงรหัสผ่าน</button>
<p data-out>แสดงรหัสผ่าน: {show ? "เปิด" : "ปิด"}</p>
</div>
);
}
export const steps = ["#toggle"];
المخرجات الفعلية
ข้อความเริ่มต้น: แสดงรหัสผ่าน: ปิด
หลังคลิกครั้งที่ 1: แสดงรหัสผ่าน: เปิด
المثال 3: التحقق من صحة المدخلات
تعيد الدالة validate قائمة برسائل الخطأ. تُعرض الرسائل بعد أن يرسل المستخدم النموذج فقط، حتى لا يزعجه النموذج أثناء الكتابة.
import React, { useState } from "react";
// กติกาการตรวจสอบ: คืนรายการข้อความแจ้งเตือน ถ้าไม่มีข้อผิดพลาดจะได้อาร์เรย์ว่าง
export function validate(email, password) {
const errors = [];
if (!email) errors.push("กรอกอีเมล");
else if (!/^\S+@\S+\.\S+$/.test(email)) errors.push("รูปแบบอีเมลไม่ถูกต้อง");
if (password.length < 6) errors.push("รหัสผ่านอย่างน้อย 6 ตัว");
return errors;
}
export function App() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [submitted, setSubmitted] = useState(false);
const errors = validate(email, password);
return (
<div>
<input value={email} onChange={e => setEmail(e.target.value)} />
<input value={password} onChange={e => setPassword(e.target.value)} />
<button id="submit" onClick={() => setSubmitted(true)}>ส่งฟอร์ม</button>
<p data-out>{!submitted ? "ยังไม่ส่งฟอร์ม" : errors.length ? errors.join(" | ") : "ไม่มีข้อผิดพลาด"}</p>
</div>
);
}
export const steps = ["#submit"];
المخرجات الفعلية
ข้อความเริ่มต้น: ยังไม่ส่งฟอร์ม
หลังคลิกครั้งที่ 1: กรอกอีเมล | รหัสผ่านอย่างน้อย 6 ตัว
المثال 4: محاكاة تسجيل الدخول
نقارن البريد وكلمة المرور ببيانات تجريبية ثم نعرض رسالة نجاح أو رسالة بأن الحساب غير موجود. يبدأ هذا المثال بقيم صحيحة.
import React, { useState } from "react";
// ตัวอย่างฐานข้อมูลผู้ใช้จำลอง จริง ๆ ต้องตรวจที่เซิร์ฟเวอร์เท่านั้น
const USERS = { "[email protected]": "secret1" };
export function login(email, password) {
if (USERS[email] === undefined) return { ok: false, message: "ไม่พบบัญชีนี้" };
if (USERS[email] !== password) return { ok: false, message: "รหัสผ่านไม่ถูกต้อง" };
return { ok: true, message: "เข้าสู่ระบบสำเร็จ: " + email };
}
export function App() {
const [email, setEmail] = useState("[email protected]");
const [password, setPassword] = useState("secret1");
const [result, setResult] = useState("ยังไม่ได้ลองเข้าสู่ระบบ");
return (
<div>
<input value={email} onChange={e => setEmail(e.target.value)} />
<input value={password} onChange={e => setPassword(e.target.value)} />
<button id="submit" onClick={() => setResult(login(email, password).message)}>เข้าสู่ระบบ</button>
<p data-out>{result}</p>
</div>
);
}
export const steps = ["#submit"];
المخرجات الفعلية
ข้อความเริ่มต้น: ยังไม่ได้ลองเข้าสู่ระบบ
หลังคลิกครั้งที่ 1: เข้าสู่ระบบสำเร็จ: [email protected]
المثال 5: القفل بعد محاولات فاشلة متكررة
نعدّ مرات كلمة المرور الخاطئة، ونعطّل الزر عند بلوغ الحد، لإبطاء التخمين المتكرر.
import React, { useState } from "react";
const MAX_TRIES = 3;
export function App() {
const [password, setPassword] = useState("wrong");
const [tries, setTries] = useState(0);
const [message, setMessage] = useState("");
const locked = tries >= MAX_TRIES;
function submit() {
if (locked) return;
if (password === "secret1") {
setMessage("เข้าสู่ระบบสำเร็จ");
return;
}
const next = tries + 1;
setTries(next);
setMessage(next >= MAX_TRIES ? "ล็อกชั่วคราว ลองใหม่ภายหลัง" : `รหัสผ่านไม่ถูกต้อง (ครั้งที่ ${next} จาก ${MAX_TRIES})`);
}
return (
<div>
<input value={password} onChange={e => setPassword(e.target.value)} />
<button id="submit" disabled={locked} onClick={submit}>เข้าสู่ระบบ</button>
<p data-out>{message || "พร้อมใช้งาน"}</p>
</div>
);
}
export const steps = ["#submit", "#submit", "#submit"];
المخرجات الفعلية
ข้อความเริ่มต้น: พร้อมใช้งาน
หลังคลิกครั้งที่ 1: รหัสผ่านไม่ถูกต้อง (ครั้งที่ 1 จาก 3)
หลังคลิกครั้งที่ 2: รหัสผ่านไม่ถูกต้อง (ครั้งที่ 2 จาก 3)
หลังคลิกครั้งที่ 3: ล็อกชั่วคราว ลองใหม่ภายหลัง
المثال 6: خيار تذكّرني
خانة اختيار تتحكم بها الحالة، ويظهر وضعها للمستخدم فورًا.
import React, { useState } from "react";
export function App() {
const [remember, setRemember] = useState(false);
return (
<div>
<label>
<input id="remember" type="checkbox" checked={remember} onChange={e => setRemember(e.target.checked)} />
จดจำการเข้าสู่ระบบ
</label>
<p data-out>จดจำการเข้าสู่ระบบ: {remember ? "เปิด" : "ปิด"}</p>
</div>
);
}
export const steps = ["#remember"];
المخرجات الفعلية
ข้อความเริ่มต้น: จดจำการเข้าสู่ระบบ: ปิด
หลังคลิกครั้งที่ 1: จดจำการเข้าสู่ระบบ: เปิด
المثال 7: حفظ حالة المستخدم بـ Context
ننشئ AuthContext حتى تصل شريطة الحالة وأزرارها إلى بيانات المستخدم دون تمرير props عبر عدة مستويات.
import React, { createContext, useContext, useState } from "react";
// เก็บสถานะผู้ใช้ไว้ที่ Context เพื่อให้ทุกส่วนของหน้าเข้าถึงได้โดยไม่ต้องส่ง props ต่อกันหลายชั้น
const AuthContext = createContext(null);
function StatusBar() {
const { user, login, logout } = useContext(AuthContext);
return (
<div>
<p data-out>สถานะ: {user ? "สวัสดี " + user : "ยังไม่ได้เข้าสู่ระบบ"}</p>
{user
? <button id="logout" onClick={logout}>ออกจากระบบ</button>
: <button id="login" onClick={() => login("สมชาย")}>เข้าสู่ระบบ</button>}
</div>
);
}
export function App() {
const [user, setUser] = useState(null);
const value = { user, login: setUser, logout: () => setUser(null) };
return (
<AuthContext.Provider value={value}>
<StatusBar />
</AuthContext.Provider>
);
}
export const steps = ["#login", "#logout"];
المخرجات الفعلية
ข้อความเริ่มต้น: สถานะ: ยังไม่ได้เข้าสู่ระบบ
หลังคลิกครั้งที่ 1: สถานะ: สวัสดี สมชาย
หลังคลิกครั้งที่ 2: สถานะ: ยังไม่ได้เข้าสู่ระบบ
المثال 8: صفحة تتطلب تسجيل الدخول
إذا لم يوجد مستخدم تُطلب منه الدخول، وإذا وُجد تُعرض لوحة التحكم. هذه فكرة المسارات المحمية.
1. سجّل الدخول ثم اخرج من الحساب
import React, { useState } from "react";
// หน้าที่ต้องเข้าสู่ระบบก่อนจึงจะเห็น ถ้ายังไม่มีผู้ใช้จะแสดงข้อความให้เข้าสู่ระบบแทน
function Dashboard({ user }) {
if (!user) return <p data-out>กรุณาเข้าสู่ระบบก่อน</p>;
return <p data-out>หน้าแดชบอร์ดของ {user}</p>;
}
export function App() {
const [user, setUser] = useState(null);
return (
<div>
<button id="login" onClick={() => setUser("สมชาย")}>เข้าสู่ระบบ</button>
<button id="logout" onClick={() => setUser(null)}>ออกจากระบบ</button>
<Dashboard user={user} />
</div>
);
}
export const steps = ["#login", "#logout"];
المخرجات الفعلية
ข้อความเริ่มต้น: กรุณาเข้าสู่ระบบก่อน
หลังคลิกครั้งที่ 1: หน้าแดชบอร์ดของ สมชาย
หลังคลิกครั้งที่ 2: กรุณาเข้าสู่ระบบก่อน
المثال 9: صفحة تسجيل الدخول المكتملة
الصفحة النهائية تجمع النموذج وحقول الإدخال مع تلميحات autocomplete الصحيحة وخيار التذكّر ورابط نسيت كلمة المرور.
import React from "react";
// หน้าเข้าสู่ระบบแบบสมบูรณ์ที่รวมทุกส่วนไว้ด้วยกัน: ฟอร์ม ช่องกรอก ตัวเลือกจดจำ และลิงก์ลืมรหัสผ่าน
export function App() {
return (
<main>
<h1>เข้าสู่ระบบ</h1>
<form>
<label>อีเมล<input type="email" name="email" autoComplete="username" /></label>
<label>รหัสผ่าน<input type="password" name="password" autoComplete="current-password" /></label>
<label><input type="checkbox" name="remember" /> จดจำการเข้าสู่ระบบ</label>
<button type="submit">เข้าสู่ระบบ</button>
</form>
<a href="/forgot">ลืมรหัสผ่าน?</a>
</main>
);
}
المخرجات الفعلية
<main><h1>เข้าสู่ระบบ</h1><form><label>อีเมล<input type="email" name="email" autoComplete="username"/></label><label>รหัสผ่าน<input type="password" name="password" autoComplete="current-password"/></label><label><input type="checkbox" name="remember"/> จดจำการเข้าสู่ระบบ</label><button type="submit">เข้าสู่ระบบ</button></form><a href="/forgot">ลืมรหัสผ่าน?</a></main>
الخطوات التالية
الخطوة التالية ربط صفحة الدخول بواجهة برمجية حقيقية باستخدام fetch، واستخدام ملف تعريف ارتباط HTTP-only أو رمز آمن، وإضافة استعادة كلمة المرور عبر رابط يُرسل بالبريد.
الخلاصة
صفحة الدخول الجيدة تجمع بين نموذج تتحكم به الحالة، والتحقق من المدخلات، وتحديد عدد المحاولات الفاشلة، ومشاركة حالة المستخدم عبر Context، وصفحة تتحقق من الدخول قبل عرض البيانات. يجب أن يتحقق النظام الحقيقي من الصلاحيات على الخادم دائمًا.