HTML Semantic Elements
HTML5 นำเสนอ Semantic Elements ซึ่งเป็น Tags ที่มีความหมายในตัวเอง
ช่วยให้ browser, Search Engine และ Screen Reader เข้าใจโครงสร้างหน้าเว็บได้ดีขึ้น
แทนที่จะใช้ <div> สำหรับทุกอย่าง ให้เลือก Tag ที่ตรงกับความหมาย:
<header>— ส่วนหัวของหน้าหรือ section (Logo, Navigation, Hero)<main>— เนื้อหาหลักของหน้า ใช้ได้เพียง 1 อันต่อหน้า<section>— ส่วนของเนื้อหาที่มีหัวข้อรวมกัน เช่น Hero, Services, Contact<article>— เนื้อหาที่เป็นอิสระ อ่านได้โดยไม่ต้องอิงบริบทรอบข้าง เช่น บทความ Blog<aside>— เนื้อหาเสริมข้างเคียง เช่น Sidebar, Related Links<footer>— ส่วนท้ายของหน้าหรือ section (ลิขสิทธิ์, ลิงก์ Social)<nav>— เมนูนำทางหลัก ห่อรายการลิงก์ที่ใช้นำทางหน้า
ใน BookEasy (โปรเจกต์หลักของรายวิชา) หน้า Landing Page จะใช้โครงสร้าง:
<header> (ชื่อแอป + nav) →
<main> ที่มี <section> ย่อย (Hero, Services, CTA) →
<footer> (copyright)
ใช้ Semantic Elements แทน <div> ทั่วไปเสมอเมื่อมีความหมายที่ตรงกัน
เช่น ใช้ <nav> แทน <div class="nav">
ช่วยให้ Screen Reader อ่านหน้าเว็บได้ถูกต้องและ SEO ดีขึ้น
CSS Flexbox & Grid เบื้องต้น
Flexbox เหมาะสำหรับ Layout 1 มิติ (แถวหรือคอลัมน์) เช่น Navigation bar, รายการ Card ที่ต้องการตัดบรรทัดอัตโนมัติ:
display: flex— เปิดใช้ Flexbox บน containerflex-direction: row | column— ทิศทางการวาง itemsjustify-content: flex-start | center | space-between— จัดตำแหน่งในแนวแกนหลักalign-items: flex-start | center | stretch— จัดตำแหน่งในแนวตั้งฉากflex-wrap: wrap— อนุญาตให้ items ตัดบรรทัดใหม่เมื่อเต็มแถวgap— ระยะห่างระหว่าง items โดยไม่ต้องใช้ margin
CSS Grid เหมาะสำหรับ Layout 2 มิติ (แถวและคอลัมน์พร้อมกัน) เช่น Layout หน้าหลักที่มี Sidebar + Content หรือ Services grid 3 คอลัมน์:
display: grid— เปิดใช้ Gridgrid-template-columns: repeat(3, 1fr)— 3 คอลัมน์ขนาดเท่ากันgrid-template-columns: repeat(auto-fill, minmax(280px, 1fr))— ปรับจำนวนคอลัมน์อัตโนมัติgap— ระยะห่างระหว่าง grid cellsgrid-column: span 2— ขยาย item ให้ครอบ 2 คอลัมน์
ใช้ @media (max-width: 768px) เปลี่ยน Layout เป็น 1 คอลัมน์บน Mobile
เพื่อให้หน้าเว็บ Responsive รองรับทุกขนาดหน้าจอ
ES2022 ที่ใช้บ่อยใน Stack นี้
JavaScript มาตรฐานสมัยใหม่ (ES2015+ หรือที่เรียกรวมว่า ESNext/ES2022) มีฟีเจอร์ที่ทำให้ เขียน code ได้กระชับและอ่านง่ายขึ้นมาก ทุก Stack ที่ใช้ในรายวิชา (React, Hono, Supabase) ล้วนพึ่งพาฟีเจอร์เหล่านี้:
-
Arrow Functions — ไวยากรณ์กระชับสำหรับฟังก์ชัน
const add = (a, b) => a + bต่างจาก function ปกติตรงที่ไม่มีthisbinding ของตัวเอง จึงเหมาะมากสำหรับ callback ใน array methods และ event handlers -
Destructuring — แตกค่าออกจาก Object หรือ Array
ด้วย
const { name, price } = service(object) และconst [first, ...rest] = services(array) ทำให้ไม่ต้องเข้าถึง property ซ้ำๆ ด้วย dot notation -
Spread & Rest Operators (
...) — คัดลอกและรวม Object/Array โดยไม่ mutate ต้นฉบับ ใช้มากใน React state updates:const updated = { ...service, price: 500 } -
Optional Chaining (
?.) — เข้าถึง nested properties อย่างปลอดภัย โดยไม่เกิด TypeError หาก property ใด undefined:user?.profile?.phone -
Nullish Coalescing (
??) — ให้ค่า default เมื่อ null หรือ undefined:phone ?? 'ไม่ระบุ'ต่างจาก||ตรงที่0และ''ไม่ถูกนับว่าเป็น falsy -
Template Literals — String ที่ฝัง expression ได้ด้วย backtick:
`บริการ ${name} ราคา ${price} บาท`รองรับ multiline โดยไม่ต้องต่อ string ด้วย+
ฟีเจอร์ทั้งหมดนี้ใช้ได้ใน browser ทุกตัวที่ทันสมัย (Chrome, Firefox, Safari, Edge) และ Node.js v14+ รวมถึง Cloudflare Workers ไม่ต้องใช้ Babel transpile แล้วในปัจจุบัน
async/await และ Fetch API
JavaScript เป็น Single-threaded ใช้ Asynchronous เพื่อทำงานที่ใช้เวลา
เช่น ดึงข้อมูลจาก API โดยไม่บล็อก UI Promises เป็นกลไกพื้นฐาน
และ async/await เป็น syntax ที่ทำให้เขียนและอ่าน Promise ได้ง่ายขึ้น:
-
async function— ประกาศฟังก์ชันที่ทำงาน asynchronous ฟังก์ชัน async จะ return Promise เสมอ -
await— หยุดรอผล Promise ก่อนทำบรรทัดถัดไป ใช้ได้เฉพาะภายในasync functionหรือ top-level module -
try / catch— จัดการ Error ที่เกิดจาก await หาก fetch ล้มเหลวหรือ server คืน error ต้องจับด้วย catch
Fetch API เป็น built-in ใน browser สำหรับส่ง HTTP request
ทำงานด้วย Promises และใช้ร่วมกับ async/await ได้ตรงๆ:
const response = await fetch('/api/services')
จากนั้นแปลงเป็น JSON ด้วย await response.json()
fetch จะไม่ throw Error เมื่อ server ตอบ 4xx หรือ 5xx
ต้องตรวจสอบ response.ok หรือ response.status ด้วยตัวเอง
แล้ว throw new Error(...) เพื่อให้ catch จัดการได้ถูกต้อง
ES Modules: import / export
ES Modules คือระบบ module มาตรฐานของ JavaScript ที่ช่วยแบ่งโค้ดออกเป็นไฟล์ย่อย แต่ละไฟล์มี scope ของตัวเอง ไม่มีการปนกันของตัวแปร React, Hono และทุกเครื่องมือใน stack นี้ล้วนใช้ระบบ module นี้:
-
Named export —
export const BASE_URL = '...'และexport function getServices() {}import ด้วยimport { getServices, BASE_URL } from './services.js' -
Default export —
export default function App() {}import ด้วยimport App from './App.js'(ตั้งชื่อเองได้) -
ใช้ใน browser — เพิ่ม
type="module"บน script tag:<script type="module" src="main.js"></script>browser จะโหลด dependency ทั้งหมดอัตโนมัติ
ข้อสำคัญ: ES Modules ต้องรันผ่าน HTTP server เท่านั้น ไม่สามารถเปิดไฟล์ด้วย
file:// โดยตรงได้เพราะ CORS policy ของ browser
ในสัปดาห์นี้ใช้ python3 -m http.server 8080 แทน Vite
(Vite จะเริ่มใช้จริงตั้งแต่สัปดาห์ที่ 3)
// snippet 1: ES2022 Modern JavaScript
// Arrow functions
const add = (a, b) => a + b;
// Destructuring
const { name, price } = service;
const [first, ...rest] = services;
// Spread operator
const updated = { ...service, price: 500 };
// Optional chaining & nullish coalescing
const phone = user?.profile?.phone ?? 'ไม่ระบุ';
// Template literals
const msg = `บริการ ${name} ราคา ${price} บาท`;
// snippet 2: Async/Await + Fetch API
async function fetchServices() {
try {
const response = await fetch('/api/services');
if (!response.ok) throw new Error(`HTTP error: ${response.status}`);
const data = await response.json();
return data;
} catch (error) {
console.error('โหลดข้อมูลล้มเหลว:', error);
return [];
}
}
// snippet 3: ES Modules
// services.js — export
export const BASE_URL = 'http://localhost:8787';
export async function getServices() {
const res = await fetch(`${BASE_URL}/api/services`);
return res.json();
}
// main.js — import
import { getServices, BASE_URL } from './services.js';
const services = await getServices();
console.log(services);
🧪 ปฏิบัติการ Lab 1 — BookEasy: หน้า Landing Page (Static)
สร้างหน้า Landing Page ของ BookEasy ด้วย HTML/CSS บริสุทธิ์ และเพิ่ม JavaScript เบื้องต้น พร้อมทดสอบ ES Modules ยังไม่มี React หรือ Vite ในสัปดาห์นี้
ยังไม่ใช้ Vite ในสัปดาห์นี้ ใช้ python3 -m http.server 8080 เพื่อรัน ES Modules
แล้วเปิดเบราว์เซอร์ที่ http://localhost:8080
สร้าง Landing Page ของ BookEasy ด้วย HTML และ CSS บริสุทธิ์ โดยยังไม่มี JavaScript ในขั้นตอนนี้
- สร้าง folder
bookeasy-static/ - สร้าง
bookeasy-static/index.htmlที่มี<header>(ชื่อแอป + nav),<main>ที่ประกอบด้วย Hero section, Services section (การ์ด dummy 3 ใบ), และ<footer> - สร้าง
bookeasy-static/style.cssสำหรับ layout ด้วย Flexbox/Grid ครอบคลุม Header, Services grid, Footer - เชื่อม CSS เข้ากับ HTML ด้วย
<link rel="stylesheet" href="style.css">
เปิด http://localhost:8080 แล้วเห็นหน้า Landing Page ที่มีครบ: Header, Hero, Services (3 การ์ด), Footer ใช้ Semantic HTML5 Elements ถูกต้อง และ CSS Grid/Flexbox จัดหน้าเว็บได้อย่างมีระเบียบ
เริ่มจาก HTML skeleton ก่อน (ไม่มี CSS) แล้วค่อยเพิ่ม CSS ทีละส่วน ใช้ grid-template-columns: repeat(3, 1fr) สำหรับ Services grid และ display: flex; justify-content: space-between สำหรับ Header
เพิ่ม JavaScript ให้หน้าเว็บแสดงข้อมูล Services แบบ Dynamic โดยดึงจาก mock array แทนการ hardcode ใน HTML
- สร้าง
bookeasy-static/main.js - เพิ่มฟังก์ชัน
async function loadServices()ที่ใช้ hardcode array แทน fetch เช่นconst services = [{ name: 'นวดไทย', price: 350 }, ...] - ใช้
document.querySelectorและinnerHTMLหรือappendChildเพิ่ม card ลงใน DOM - เรียกใช้
loadServices()เมื่อหน้าโหลดเสร็จ
หน้า Services แสดง card จาก JavaScript array ไม่ใช่ HTML hardcode — ลบข้อมูลใน array แล้ว Reload หน้าต้องไม่เห็น card เลย และเพิ่มข้อมูลใหม่ใน array แล้ว Reload ต้องเห็น card เพิ่มขึ้น
ใช้ Arrow Function และ Template Literal ที่เรียนมา: services.forEach(s => { container.innerHTML += `<div class="card">${s.name}</div>` }) หรือจะใช้ map().join('') ก็ได้ ใส่ await ไว้เพื่อเตรียม refactor เป็น fetch จริงในภายหลัง
แยกโค้ดออกเป็น module และใช้ ES Modules import / export เพื่อฝึกโครงสร้างที่ใช้จริงในโปรเจกต์ React/Hono
- สร้าง
bookeasy-static/services.jsสำหรับ mock data และ helper functions - ใน
services.jsประกาศexport const MOCK_SERVICES = [...]และexport function renderCards(data) {} - แก้
main.jsให้ import จาก services.js:import { MOCK_SERVICES, renderCards } from './services.js' - แก้ tag
<script>ใน HTML เป็น<script type="module" src="main.js"></script>
หน้าเว็บยังแสดงผล Services card ได้ปกติหลังเปลี่ยนเป็น module และในแท็บ Network ของ DevTools เห็น browser โหลดทั้ง main.js และ services.js แยกกัน ไม่มี Error ใน Console
หาก Console แสดง CORS error ให้ตรวจสอบว่ารันผ่าน python3 -m http.server 8080 แล้ว ไม่ใช่เปิดไฟล์โดยตรงด้วย file:// และตรวจสอบว่า import path ใช้ ./services.js (มี ./ นำหน้าและนามสกุล .js ครบ)