🔄 Tóm tắt nhanh: Trong bài học trước, bạn đã thêm tính tương tác bằng JavaScript thuần túy — thao tác DOM, xử lý sự kiện và xác thực biểu mẫu. Bây giờ, hãy kết nối với các nguồn dữ liệu bên ngoài và xây dựng các tính năng thực sự năng động.
Lấy dữ liệu từ API
Mẫu gọi API cơ bản
📍 Nơi dán: Mở ChatGPT (chat.openai.com), Claude (claude.ai) hoặc Gemini (gemini.google.com) và bắt đầu một cuộc trò chuyện mới.
📋 Cách sao chép prompt này: Nhấp vào bất kỳ đâu bên trong khối màu xám, nhấn Cmd+A rồi Cmd+C (Mac) hoặc Ctrl+A rồi Ctrl+C (Windows). Hoặc sử dụng biểu tượng sao chép xuất hiện.
Viết code JavaScript thuần túy để lấy dữ liệu từ API REST:
Endpoint API: [URL hoặc mô tả API]
Định dạng phản hồi dự kiến: Mảng JSON các đối tượng với [mô tả các trường]
Yêu cầu:
- Sử dụng async/await với fetch()
- Hiển thị chỉ báo load trong khi lấy dữ liệu
- Xử lý lỗi HTTP (kiểm tra response.ok)
- Xử lý lỗi mạng (sử dụng try-catch xung quanh hàm fetch)
- Hiển thị dữ liệu trên trang bằng các phương thức DOM an toàn (createElement + textContent)
- Hiển thị thông báo lỗi thân thiện với người dùng nếu việc lấy dữ liệu thất bại
- Thêm thời gian chờ (dừng sau 10 giây bằng AbortController)
JavaScript thuần túy, không có thư viện dependency.
✏️ Cách điền thông tin chi tiết của bạn: Thay thế mỗi [] và trình giữ chỗ trong ngoặc bằng thông tin cụ thể từ tình huống thực tế của bạn. Đầu vào mơ hồ sẽ tạo ra đầu ra mơ hồ — hãy cụ thể.
👀 Những gì bạn sẽ thấy: Trong vòng vài giây, AI sẽ trả về phản hồi có cấu trúc dựa vào prompt ở trên. Hãy đọc kỹ và coi đó là bản nháp, không phải câu trả lời cuối cùng.
📌 Cách xử lý kết quả: Lưu phản hồi vào file Notes. Chọn đề xuất có hiệu quả cao nhất và thực hiện ngay trong tuần này — đừng cố gắng làm tất cả cùng một lúc.
⚠️ Nếu kết quả không ổn: Nếu các đề xuất có vẻ chung chung, hãy dán nội dung sau: "Hãy cụ thể hơn với ngữ cảnh thực tế của tôi. Bỏ qua những lời khuyên chung chung." Nếu bỏ qua các chi tiết quan trọng bạn đã cung cấp, hãy hỏi: "Bạn đã bỏ sót [X] trong ngữ cảnh của tôi — hãy thực hiện lại với điều đó làm ràng buộc chính."
Trạng thái loading, lỗi và trống
Mỗi tính năng dựa trên dữ liệu cần 3 trạng thái ngoài trạng thái thành công:
Viết JavaScript thuần túy để quản lý các trạng thái giao diện người dùng cho một tính năng được hỗ trợ bởi API:
Các trạng thái cần xử lý:
1. Đang load: Hiển thị biểu tượng load/khung hình trong khi dữ liệu đang load
2. Thành công: Hiển thị dữ liệu
3. Lỗi: Hiển thị thông báo thân thiện với nút thử lại
4. Trống: Hiển thị thông báo "không có kết quả" khi API trả về một mảng trống
Yêu cầu:
- Một hàm duy nhất quản lý các chuyển đổi trạng thái
- Nút thử lại sẽ load lại dữ liệu
- Biểu tượng load có thể truy cập được (thông báo aria-live="polite")
- Thông báo lỗi không tiết lộ chi tiết kỹ thuật cho người dùng
Cung cấp cấu trúc HTML, CSS cho mỗi trạng thái và JavaScript.
✅ Kiểm tra nhanh: Tại sao phải xử lý trạng thái "trống" riêng biệt với trạng thái "thành công"?
Vì kết quả trống khác với kết quả thành công có dữ liệu. Nếu người dùng tìm kiếm "xyzabc" và không nhận được kết quả nào, việc hiển thị cho họ một trang trống sẽ tạo cảm giác không ổn. Hiển thị "Không tìm thấy kết quả cho 'xyzabc' — hãy thử từ khóa tìm kiếm khác" cho người dùng biết rằng tìm kiếm đã thành công nhưng không có kết quả nào khớp. Trạng thái trống là một cơ hội để giao tiếp: Giúp người dùng hiểu điều gì đã xảy ra và nên thử gì tiếp theo.
Giao diện tìm kiếm và lọc
Tìm kiếm phía client
Viết bộ lọc tìm kiếm thời gian thực bằng JavaScript thuần túy:
Dữ liệu: Một mảng các đối tượng được hiển thị dưới dạng thẻ trên trang
Hành vi tìm kiếm:
- Lọc khi người dùng nhập (được xử lý bằng debounce, độ trễ 300ms)
- Tìm kiếm trên nhiều trường: [tiêu đề, mô tả, danh mục]
- So khớp không phân biệt chữ hoa chữ thường
- Hiển thị số lượng kết quả ("Hiển thị X trong số Y mục")
- Hiển thị thông báo "Không có kết quả" khi không có gì khớp
- Nút xóa để reset tìm kiếm
Yêu cầu:
- Xử lý đầu vào bằng debounce (không lọc trên mỗi lần nhấn phím)
- Highlight văn bản khớp trong kết quả (bao bọc các kết quả khớp trong phần tử đánh dấu)
- Duy trì trạng thái URL (thêm truy vấn tìm kiếm dưới dạng tham số URL để nó vẫn tồn tại sau khi làm mới trang)
- Dễ tiếp cận: thông báo thay đổi số lượng kết quả cho trình đọc màn hình
JavaScript thuần túy, không có thư viện dependency.
Giao diện đa bộ lọc
Viết hệ thống lọc cho trang danh sách:
Các loại bộ lọc:
- Danh mục (hộp kiểm, chọn nhiều)
- Độ khó (nút radio, chọn một)
- Sắp xếp (menu drop-down: mới nhất, cũ nhất, theo thứ tự bảng chữ cái)
Yêu cầu:
- Các bộ lọc kết hợp với logic AND (danh mục VÀ độ khó)
- URL cập nhật theo trạng thái bộ lọc (URL bộ lọc có thể chia sẻ)
- Hiển thị số lượng bộ lọc đang hoạt động ("3 bộ lọc đang hoạt động")
- Nút "Xóa tất cả bộ lọc"
- Chuyển đổi mượt mà khi các mục hiển thị/ẩn
- Số lượng bộ lọc cập nhật để hiển thị các mục có sẵn cho mỗi tùy chọn
JavaScript thuần túy, không có thư viện dependency.
Xây dựng các thành phần tương tác phổ biến
Tabs
Viết một thành phần tab dễ truy cập bằng JavaScript thuần túy:
Yêu cầu:
- Danh sách tab với role="tablist"
- Các nút tab với role="tab", aria-selected, aria-controls
- Bảng tab với role="tabpanel", aria-labelledby
- Điều hướng bằng bàn phím: Phím mũi tên di chuyển giữa các tab, Enter/Space chọn
- Chỉ bảng tab được chọn mới hiển thị
- Hỗ trợ nội dung tab động (được load khi chuyển đổi tab)
Tuân theo mẫu WAI-ARIA Tabs. JavaScript thuần túy, không có thư viện dependency.
Modal/Dialog
Viết một hộp thoại modal dễ truy cập bằng JavaScript thuần túy:
Yêu cầu:
- Mở khi nhấp vào nút kích hoạt
- Giữ tiêu điểm bên trong modal (Phím Tab chỉ di chuyển qua các phần tử modal)
- Đóng khi nhấn phím Escape, nhấp vào lớp phủ và nút đóng
- Trả tiêu điểm về nút kích hoạt khi đóng
- Sử dụng role="dialog" và aria-modal="true"
- Ngăn cuộn nền khi mở
- Hoạt ảnh mở/đóng mượt mà bằng CSS
Tuân theo mẫu WAI-ARIA Dialog. JavaScript thuần túy, không có thư viện dependency.
✅ Kiểm tra nhanh: Tại sao việc giữ tiêu điểm lại quan trọng trong modal?
Nếu không có cơ chế giữ tiêu điểm (focus trapping), người dùng bàn phím khi nhấn phím Tab trong cửa sổ modal sẽ vô tình chuyển đến trang nền ẩn. Họ có thể tương tác với các nút và liên kết mà họ không nhìn thấy phía sau lớp phủ modal. Điều này gây nhầm lẫn, tiềm ẩn nguy hiểm (họ có thể gửi một biểu mẫu mà họ không có ý định), và vi phạm các nguyên tắc về khả năng tiếp cận. Cơ chế giữ tiêu điểm đảm bảo người dùng bàn phím sẽ ở lại trong cửa sổ modal cho đến khi họ chủ động đóng nó.
Bài tập: Xây dựng một tính năng động
Tạo giao diện tìm kiếm và lọc cho danh sách các mục (ít nhất 12 mục)
Lấy dữ liệu từ API công cộng miễn phí (ví dụ: JSONPlaceholder) với trạng thái load và lỗi
Xây dựng một thành phần tabs dễ truy cập để sắp xếp nội dung
Đảm bảo tất cả các tính năng hoạt động với điều hướng chỉ bằng bàn phím
Kiểm tra trạng thái lỗi bằng cách tạm thời sử dụng URL API sai
Những điểm chính cần ghi nhớ
async/await giúp các lệnh gọi API dễ đọc và dễ gỡ lỗi — nên ưu tiên sử dụng nó hơn những chuỗi .then() lồng nhau
Mỗi lệnh gọi API cần có 3 trạng thái: đang load (biểu tượng chờ), lỗi (thông báo thân thiện + thử lại) và trống (hướng dẫn hữu ích)
Không bao giờ để lộ API key trong code phía client — hãy sử dụng proxy phía server cho bất kỳ lệnh gọi API nào được xác thực
Gây nhiễu đầu vào tìm kiếm (độ trễ 300ms) để tránh lọc trên mỗi lần nhấn phím, điều này làm giảm hiệu suất
Các thành phần tương tác (tab, modal, accordion) phải tuân theo những mẫu WAI-ARIA để dễ truy cập bằng bàn phím và trình đọc màn hình
Cập nhật URL với trạng thái lọc/tìm kiếm để người dùng có thể chia sẻ và đánh dấu các chế độ xem cụ thể
Câu 1:
Bạn nên xử lý API key trong JavaScript phía client như thế nào?
GIẢI THÍCH:
Mọi dòng JavaScript phía client đều hiển thị cho bất kỳ ai mở công cụ dành cho nhà phát triển trình duyệt. Mã hóa, biến môi trường trong bản build frontend và localStorage đều có thể đọc được bởi người dùng. Cách tiếp cận an toàn duy nhất: đặt API key của bạn trong một hàm phía server (Netlify Functions, Vercel Edge, AWS Lambda), và để frontend gọi server CỦA BẠN, server này sẽ gọi API với key bí mật. Key của bạn không bao giờ chạm đến trình duyệt.
Câu 2:
Bạn luôn nên bao gồm những gì khi lấy dữ liệu từ API?
GIẢI THÍCH:
Nếu không có trạng thái load, người dùng nhấp chuột và không thấy gì — họ sẽ cho rằng trang bị lỗi và nhấp chuột lại. Nếu không có xử lý lỗi, một lệnh gọi API thất bại sẽ hiển thị trang trống hoặc lỗi JavaScript khó hiểu. Nếu không có thời gian chờ, một API chậm có thể khiến người dùng phải chờ hơn 30 giây mà không có phản hồi. Ba yếu tố này biến một lệnh gọi API dễ bị lỗi thành trải nghiệm người dùng đáng tin cậy.
Câu 3:
Tại sao nên sử dụng async/await thay vì chuỗi .then() cho các lệnh gọi API?
GIẢI THÍCH:
So sánh: fetch(url).then(r => r.json()).then(data => { fetch(url2).then(r2 => r2.json()).then(data2 => {...})}). Bây giờ, hãy so sánh: const r = await fetch(url); const data = await r.json(); const r2 = await fetch(url2); const data2 = await r2.json(). Phiên bản async/await đọc từ trên xuống dưới — mỗi dòng xảy ra sau khi dòng trước đó hoàn thành. Không lồng nhau, không chuỗi gọi lại, và xử lý lỗi hoạt động với một khối try-catch duy nhất thay vì nhiều trình xử lý .catch().
Theo Nghị định 147/2024/ND-CP, bạn cần xác thực tài khoản trước khi sử dụng tính năng này. Chúng tôi sẽ gửi mã xác thực qua SMS hoặc Zalo tới số điện thoại mà bạn nhập dưới đây: