Study Guide: JavaScript + DOM + State
JavaScript พื้นฐาน
Section titled “JavaScript พื้นฐาน”JavaScript คือภาษาที่ทำให้เว็บ มีชีวิต — กดปุ่มแล้วเกิดอะไรบางอย่าง, กรอกฟอร์มแล้ว validate, โหลดข้อมูลมาแสดง
ลองเล่นก่อน!
Section titled “ลองเล่นก่อน!”const name = "Alex"; let score = 0; score += 10; const fruits = ["apple", "banana"]; typeof 42; typeof "hello"; typeof true; กด Run เพื่อรันโค้ดแต่ละบรรทัด -- ผลลัพธ์จะแสดงเหมือน browser console
Hello World
This is a paragraph element.
กดแต่ละปุ่มเพื่อเปลี่ยน DOM แบบ real-time -- สังเกตว่า JavaScript เข้าถึง HTML ได้โดยตรง
แต่ละ card ใช้ event ต่างกัน -- นี่คือวิธีที่ JavaScript ฟังการกระทำของ user
กดปุ่มแล้วดู State Log -- ทุกครั้งที่ state เปลี่ยน UI จะ render ใหม่อัตโนมัติ
Variables — กล่องเก็บข้อมูล
Section titled “Variables — กล่องเก็บข้อมูล”// const = ค่าไม่เปลี่ยน (ใช้เป็น default)const name = "Alex";const age = 25;
// let = ค่าเปลี่ยนได้let score = 0;score = score + 10; // score = 10
// ห้ามใช้ var — มันเป็น legacy แล้วNaming Conventions — การตั้งชื่อตัวแปร
Section titled “Naming Conventions — การตั้งชื่อตัวแปร”การตั้งชื่อตัวแปรมีหลาย style — แต่ละทีมจะเลือกใช้แบบที่ตกลงกัน สิ่งสำคัญคือ ใช้ให้สม่ำเสมอ ตลอดทั้งโปรเจกต์
| Style | ตัวอย่าง | ใช้ที่ไหน |
|---|---|---|
| camelCase | userName, isLoggedIn, getTotalPrice | JavaScript, TypeScript, Java — ใช้บ่อยที่สุดในโลก frontend |
| snake_case | user_name, is_logged_in, get_total_price | Python, Ruby, Rust, ชื่อไฟล์, database columns |
| PascalCase | UserName, ShoppingCart, TodoItem | ชื่อ Class, React Component, Type/Interface |
| kebab-case | user-name, main-content, nav-bar | CSS class, HTML attributes, URL slugs, ชื่อไฟล์ |
| SCREAMING_SNAKE_CASE | MAX_RETRY, API_BASE_URL, DEFAULT_TIMEOUT | ค่าคงที่ (constants) ที่ไม่เปลี่ยนแปลง |
// camelCase — ตัวแปรและฟังก์ชันใน JavaScriptconst firstName = "Alex";const isActive = true;const getUserProfile = () => { /* ... */ };
// PascalCase — Component และ Classclass ShoppingCart { /* ... */ }function TodoItem({ text }) { /* ... */ }
// SCREAMING_SNAKE_CASE — ค่าคงที่const MAX_ITEMS = 100;const API_BASE_URL = "https://api.example.com";
// snake_case — ข้อมูลจาก API มักมาแบบนี้const apiResponse = { user_name: "alex", // ← จาก server (snake_case) created_at: "2026-01-01",};
// แปลงเป็น camelCase ตอนใช้ใน frontendconst userName = apiResponse.user_name;Data Types
Section titled “Data Types”// String — ข้อความconst greeting = "สวัสดี";
// Number — ตัวเลขconst price = 299.50;
// Boolean — จริง/เท็จconst isLoggedIn = true;
// Array — รายการconst fruits = ["apple", "banana", "mango"];console.log(fruits[0]); // "apple"console.log(fruits.length); // 3
// Object — ข้อมูลแบบ key-valueconst user = { name: "Alex", age: 25, role: "designer",};console.log(user.name); // "Alex"Functions — ก้อนโค้ดที่เรียกใช้ซ้ำได้
Section titled “Functions — ก้อนโค้ดที่เรียกใช้ซ้ำได้”// Arrow function (แนะนำ)const greet = (name) => { return `Hello, ${name}!`;};
// เรียกใช้console.log(greet("Alex")); // "Hello, Alex!"
// แบบสั้น — ถ้ามีแค่ return บรรทัดเดียวconst double = (n) => n * 2;console.log(double(5)); // 10Array Methods ที่ใช้บ่อยมาก
Section titled “Array Methods ที่ใช้บ่อยมาก”const numbers = [1, 2, 3, 4, 5];
// .map() — แปลงทุกตัวconst doubled = numbers.map((n) => n * 2);// [2, 4, 6, 8, 10]
// .filter() — กรองเอาเฉพาะที่ผ่านเงื่อนไขconst evens = numbers.filter((n) => n % 2 === 0);// [2, 4]
// .find() — หาตัวแรกที่ตรงเงื่อนไขconst found = numbers.find((n) => n > 3);// 4
// .forEach() — วนลูปทำอะไรบางอย่างnumbers.forEach((n) => console.log(n));[1, 2, 3, 4, 5] Click a method above DOM — สะพานเชื่อม JavaScript กับ HTML
Section titled “DOM — สะพานเชื่อม JavaScript กับ HTML”DOM (Document Object Model) คือ “ต้นไม้” ของ HTML ที่ JavaScript เข้าถึงได้
เลือก Element
Section titled “เลือก Element”// เลือก 1 ตัว — ใช้ CSS selectorconst title = document.querySelector("h1");const btn = document.querySelector("#submit-btn");const card = document.querySelector(".card");
// เลือกหลายตัว — ได้ NodeList กลับมาconst items = document.querySelectorAll(".item");items.forEach((item) => { console.log(item.textContent);});เปลี่ยนแปลง Element
Section titled “เปลี่ยนแปลง Element”const title = document.querySelector("h1");
// เปลี่ยนข้อความtitle.textContent = "Hello World!";
// เปลี่ยน styletitle.style.color = "blue";title.style.fontSize = "2rem";
// เพิ่ม/ลบ classtitle.classList.add("active");title.classList.remove("hidden");title.classList.toggle("dark-mode"); // เพิ่มถ้าไม่มี, ลบถ้ามีสร้าง Element ใหม่
Section titled “สร้าง Element ใหม่”// สร้าง elementconst li = document.createElement("li");li.textContent = "New Item";li.classList.add("todo-item");
// เพิ่มเข้าไปในหน้าconst list = document.querySelector("#todo-list");list.appendChild(li);
// ลบ elementli.remove();Hello
World
Events — ฟังการกระทำของ user
Section titled “Events — ฟังการกระทำของ user”const btn = document.querySelector("#my-btn");
// คลิกbtn.addEventListener("click", () => { console.log("Button clicked!");});
// พิมพ์ใน inputconst input = document.querySelector("#search");input.addEventListener("input", (e) => { console.log(e.target.value); // ค่าที่พิมพ์});
// ส่งฟอร์มconst form = document.querySelector("#my-form");form.addEventListener("submit", (e) => { e.preventDefault(); // ป้องกันหน้ารีเฟรช! const data = new FormData(form); console.log(data.get("name"));});State — หัวใจของ Frontend
Section titled “State — หัวใจของ Frontend”State = ข้อมูลที่เปลี่ยนแปลงได้ และเมื่อเปลี่ยนแล้ว UI ต้องอัปเดตตาม
แนวคิด: State → Render → Event → State…
Section titled “แนวคิด: State → Render → Event → State…”ตัวอย่าง: Counter App
Section titled “ตัวอย่าง: Counter App”// 1. Statelet count = 0;
// 2. Render function — อ่าน state แล้วอัปเดต UIconst render = () => { document.querySelector("#count").textContent = count;};
// 3. Event handlers — เปลี่ยน state แล้ว render ใหม่document.querySelector("#plus").addEventListener("click", () => { count++; // เปลี่ยน state render(); // อัปเดต UI});
document.querySelector("#minus").addEventListener("click", () => { count--; render();});
// เรียก render ครั้งแรกrender();ตัวอย่าง: Todo List พร้อม State
Section titled “ตัวอย่าง: Todo List พร้อม State”// === State ===let todos = [];let nextId = 1;
// === Render ===const render = () => { const list = document.querySelector("#todo-list"); list.innerHTML = ""; // เคลียร์ก่อน
todos.forEach((todo) => { const li = document.createElement("li"); li.className = `todo-item ${todo.completed ? "completed" : ""}`;
li.innerHTML = ` <input type="checkbox" ${todo.completed ? "checked" : ""}> <span>${todo.text}</span> <button class="delete-btn">x</button> `;
// Toggle completed li.querySelector("input").addEventListener("change", () => { todo.completed = !todo.completed; render(); });
// Delete li.querySelector(".delete-btn").addEventListener("click", () => { todos = todos.filter((t) => t.id !== todo.id); render(); });
list.appendChild(li); });
// อัปเดตจำนวนที่เหลือ const remaining = todos.filter((t) => !t.completed).length; document.querySelector("#count").textContent = `${remaining} remaining`;};
// === Event: Add todo ===document.querySelector("#todo-form").addEventListener("submit", (e) => { e.preventDefault(); const input = document.querySelector("#todo-input"); const text = input.value.trim(); if (!text) return;
todos.push({ id: nextId++, text, completed: false }); input.value = ""; render();});
render();localStorage — บันทึกข้อมูลบน Browser
Section titled “localStorage — บันทึกข้อมูลบน Browser”localStorage เก็บข้อมูลไว้แม้ปิดหน้าเว็บ — เหมาะสำหรับ settings, todos, theme
// บันทึก (ต้องแปลงเป็น string ก่อน)localStorage.setItem("todos", JSON.stringify(todos));localStorage.setItem("theme", "dark");
// อ่าน (ต้องแปลงกลับเป็น object)const saved = localStorage.getItem("todos");if (saved) { todos = JSON.parse(saved);}
const theme = localStorage.getItem("theme") || "light";รวมกับ State Pattern
Section titled “รวมกับ State Pattern”// โหลด state จาก localStorage ตอนเริ่มต้นlet todos = JSON.parse(localStorage.getItem("todos") || "[]");
// บันทึกทุกครั้งที่ state เปลี่ยนconst save = () => { localStorage.setItem("todos", JSON.stringify(todos));};
// ใน event handler — เปลี่ยน state + save + renderdocument.querySelector("#todo-form").addEventListener("submit", (e) => { e.preventDefault(); todos.push({ id: Date.now(), text: "New todo", completed: false }); save(); // บันทึก render(); // อัปเดต UI});(empty) Dark Mode Toggle
Section titled “Dark Mode Toggle”// อ่าน theme จาก localStoragelet isDark = localStorage.getItem("theme") === "dark";
const applyTheme = () => { document.body.classList.toggle("dark", isDark); localStorage.setItem("theme", isDark ? "dark" : "light");};
document.querySelector("#theme-toggle").addEventListener("click", () => { isDark = !isDark; applyTheme();});
// ใช้ theme ตอนโหลดหน้าapplyTheme();/* CSS Variables สำหรับ theme */body { --bg: #ffffff; --text: #333333; background: var(--bg); color: var(--text); transition: background 0.3s, color 0.3s;}
body.dark { --bg: #1a1a2e; --text: #e4e4e7;}Debugging ด้วย console
Section titled “Debugging ด้วย console”การ debug คือทักษะที่สำคัญมาก — เขียนโค้ดเก่งแค่ไหนก็ต้องเจอ bug เสมอ JavaScript มีเครื่องมือ debug ใน console ที่ใช้ง่ายมาก
console methods ที่ต้องรู้
Section titled “console methods ที่ต้องรู้”| Method | ใช้ทำอะไร |
|---|---|
console.log() | แสดงค่าตัวแปรทั่วไป |
console.table() | แสดง array/object เป็นตาราง อ่านง่าย |
console.error() | แสดง error สีแดง เห็นชัด |
console.warn() | แสดง warning สีเหลือง |
console.group() / console.groupEnd() | จัดกลุ่ม log ให้เป็นระเบียบ |
// console.log() — ใช้บ่อยที่สุดconst user = { name: "Alex", role: "designer" };console.log("User:", user);console.log("Name is:", user.name);
// console.table() — สวยงาม อ่านง่ายconst todos = [ { id: 1, text: "Learn JS", done: true }, { id: 2, text: "Build app", done: false },];console.table(todos);
// console.error() — ใช้ตอนอะไรผิดพลาดconsole.error("Something went wrong!", { code: 404 });
// console.group() — จัดกลุ่ม logconsole.group("User Info");console.log("Name:", user.name);console.log("Role:", user.role);console.groupEnd();อ่าน Error Messages
Section titled “อ่าน Error Messages”error ที่เจอบ่อยที่สุดมี 2 แบบ:
// TypeError — เรียก method บนค่าที่ไม่มีlet data = undefined;data.name; // TypeError: Cannot read properties of undefined
// ReferenceError — ใช้ตัวแปรที่ยังไม่ได้สร้างconsole.log(myVariable); // ReferenceError: myVariable is not definedError Handling
Section titled “Error Handling”เมื่อเขียนโค้ดจริง ต้องคิดเสมอว่า “ถ้ามันพังจะเกิดอะไรขึ้น?” — try/catch ช่วยให้จัดการ error ได้อย่างสง่างาม แทนที่จะให้ app พังทั้งหมด
try/catch พื้นฐาน
Section titled “try/catch พื้นฐาน”try { // โค้ดที่อาจจะพัง const data = JSON.parse("this is not json");} catch (error) { // จัดการเมื่อพัง console.error("Parse failed:", error.message);}// โปรแกรมยังทำงานต่อได้ ไม่พังทั้งหมดCommon Errors และวิธีป้องกัน
Section titled “Common Errors และวิธีป้องกัน”// TypeError: Cannot read properties of undefinedconst user = undefined;console.log(user.name); // พัง!
// ตรวจสอบก่อนใช้ (Defensive Coding)const user2 = undefined;console.log(user2?.name); // undefined — ไม่พัง (optional chaining)// TypeError: todos.map is not a functionconst todos = null;todos.map((t) => t.text); // พัง!
// ให้ค่า defaultconst todos2 = null;(todos2 || []).map((t) => t.text); // [] — ไม่พังตัวอย่าง: Error Handling กับ localStorage
Section titled “ตัวอย่าง: Error Handling กับ localStorage”const loadTodos = () => { try { const saved = localStorage.getItem("todos"); return saved ? JSON.parse(saved) : []; } catch (error) { console.error("Failed to load todos:", error.message); return []; // ถ้า parse ไม่ได้ ให้ใช้ array ว่าง }};Async Basics
Section titled “Async Basics”JavaScript เป็นภาษา single-threaded — ทำได้ทีละอย่าง แต่การโหลดข้อมูลจาก server ใช้เวลา ถ้ารอจนเสร็จ UI จะค้าง ดังนั้นจึงต้องใช้ async
setTimeout — ทำอะไรหลังจากรอ
Section titled “setTimeout — ทำอะไรหลังจากรอ”console.log("Start");
setTimeout(() => { console.log("After 2 seconds");}, 2000);
console.log("End");// ลำดับ output: "Start" → "End" → "After 2 seconds"fetch() — โหลดข้อมูลจาก API
Section titled “fetch() — โหลดข้อมูลจาก API”// แบบ .then() / .catch()fetch("https://jsonplaceholder.typicode.com/users/1") .then((response) => response.json()) .then((user) => { console.log(user.name); // "Leanne Graham" }) .catch((error) => { console.error("Failed to fetch:", error.message); });async/await — เขียนอ่านง่ายกว่า
Section titled “async/await — เขียนอ่านง่ายกว่า”const loadUser = async () => { try { const response = await fetch( "https://jsonplaceholder.typicode.com/users/1" ); const user = await response.json(); console.log(user.name); // "Leanne Graham"
// อัปเดต UI document.querySelector("#user-name").textContent = user.name; document.querySelector("#user-email").textContent = user.email; } catch (error) { console.error("Failed to load user:", error.message); document.querySelector("#user-name").textContent = "Error loading data"; }};
loadUser();ตัวอย่างจริง: โหลดรายการ posts จาก API
Section titled “ตัวอย่างจริง: โหลดรายการ posts จาก API”// === State ===let posts = [];
// === Render ===const render = () => { const list = document.querySelector("#post-list"); list.innerHTML = posts .map( (post) => ` <li class="post-item"> <h3>${post.title}</h3> <p>${post.body}</p> </li> ` ) .join("");};
// === Load Data ===const loadPosts = async () => { try { document.querySelector("#status").textContent = "Loading..."; const response = await fetch( "https://jsonplaceholder.typicode.com/posts?_limit=5" ); posts = await response.json(); render(); document.querySelector("#status").textContent = ""; } catch (error) { document.querySelector("#status").textContent = "Failed to load posts"; }};
loadPosts();Best Practices & Style Guide
Section titled “Best Practices & Style Guide”เขียนโค้ดให้ “ทำงานได้” ไม่พอ — ต้อง อ่านง่าย ด้วย เพราะคนที่อ่านโค้ดเราบ่อยที่สุดคือ “ตัวเราในอนาคต”
ตั้งชื่อตัวแปรให้สื่อความหมาย
Section titled “ตั้งชื่อตัวแปรให้สื่อความหมาย”// ชื่อไม่บอกอะไรเลยconst d = new Date();const arr = ["apple", "banana"];const flag = true;// อ่านแล้วเข้าใจทันทีconst currentDate = new Date();const fruitNames = ["apple", "banana"];const isMenuOpen = true;ใช้ const เป็น default
Section titled “ใช้ const เป็น default”// ใช้ const ก่อน เปลี่ยนเป็น let เมื่อจำเป็นconst name = "Alex";const items = [1, 2, 3]; // push/pop ได้ ไม่ต้อง letlet count = 0; // ต้องเปลี่ยนค่า ใช้ letจัดโครงสร้างไฟล์
Section titled “จัดโครงสร้างไฟล์”// === 1. State (ด้านบนสุด) ===let todos = [];let filter = "all";
// === 2. Render Functions ===const render = () => { // อัปเดต UI ตาม state};
const renderCount = () => { // อัปเดตจำนวน};
// === 3. Event Handlers (ด้านล่าง) ===document.querySelector("#form").addEventListener("submit", (e) => { e.preventDefault(); // เปลี่ยน state → render});
// === 4. Initialize ===render();เขียน comment เมื่อจำเป็น
Section titled “เขียน comment เมื่อจำเป็น”// Bad — comment ทุกบรรทัด ซ้ำกับโค้ด// เพิ่ม 1 เข้า countcount = count + 1;// เรียก renderrender();
// Good — comment เฉพาะตอนอธิบาย "ทำไม"// ใช้ Date.now() แทน auto-increment เพราะต้อง unique ข้าม sessionsconst id = Date.now();Do / Don’t
Section titled “Do / Don’t”// Don't: ใช้ innerHTML กับ user input ตรงๆ (XSS risk)li.innerHTML = userInput;
// Do: ใช้ textContent สำหรับ text ธรรมดาli.textContent = userInput;// Don't: เปลี่ยน state ใน render functionconst render = () => { count++; // ห้าม! render ควรแค่แสดงผล document.querySelector("#count").textContent = count;};
// Do: เปลี่ยน state ใน event handler เท่านั้นdocument.querySelector("#btn").addEventListener("click", () => { count++; render();});// Don't: ผูก event listener ซ้ำใน renderconst render = () => { const btn = document.querySelector("#btn"); btn.addEventListener("click", handleClick); // เพิ่มทุกครั้งที่ render!};
// Do: ผูก event listener ข้างนอก render (ครั้งเดียว)document.querySelector("#btn").addEventListener("click", handleClick);const render = () => { // แค่อัปเดต UI};Node.js & npm — เตรียมพร้อมสำหรับ React & Tailwind
Section titled “Node.js & npm — เตรียมพร้อมสำหรับ React & Tailwind”ตั้งแต่ Chapter 3 เป็นต้นไป เราจะใช้เครื่องมือที่ต้อง Node.js — ดังนั้นมาติดตั้งและเข้าใจมันก่อน
Node.js คืออะไร?
Section titled “Node.js คืออะไร?”Node.js คือ JavaScript runtime — ทำให้รัน JavaScript นอก browser ได้ เครื่องมือสมัยใหม่อย่าง Vite, Tailwind, React ล้วนต้องใช้ Node.js
ติดตั้ง Node.js
Section titled “ติดตั้ง Node.js”-
ไปที่ nodejs.org
-
ดาวน์โหลดเวอร์ชัน LTS (Long Term Support) — เสถียรที่สุด
-
ติดตั้งตาม OS แล้วเปิด Terminal ตรวจสอบ:
Terminal window node --version # ควรได้ v22.x.x หรือสูงกว่าnpm --version # ควรได้ 10.x.x หรือสูงกว่า
npm คืออะไร?
Section titled “npm คืออะไร?”npm (Node Package Manager) คือ “App Store ของโค้ด” — มี library ให้โหลดใช้ฟรีกว่า 2 ล้านตัว ทุกครั้งที่เราใช้ React, Tailwind, หรือ Framer Motion เราติดตั้งผ่าน npm
# ติดตั้ง packagenpm install react # ติดตั้ง 1 ตัวnpm install react react-dom # ติดตั้งหลายตัวพร้อมกัน
# ติดตั้งแบบ dev dependency (ใช้ตอนพัฒนาเท่านั้น)npm install -D tailwindcss
# ลบ packagenpm uninstall react
# ดู packages ที่ติดตั้งแล้วnpm listpackage.json — สูตรอาหารของโปรเจกต์
Section titled “package.json — สูตรอาหารของโปรเจกต์”ทุกโปรเจกต์ที่ใช้ npm จะมีไฟล์ package.json — เก็บข้อมูลโปรเจกต์ + รายชื่อ packages ที่ใช้
{ "name": "my-project", "version": "1.0.0", "scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview" }, "dependencies": { "react": "^19.0.0", "react-dom": "^19.0.0" }, "devDependencies": { "tailwindcss": "^4.0.0", "vite": "^6.0.0" }}| Section | คืออะไร |
|---|---|
scripts | คำสั่งลัด — รัน npm run dev แทนการพิมพ์คำสั่งยาว |
dependencies | packages ที่ใช้ตอนรัน (React, etc.) |
devDependencies | packages ที่ใช้ตอนพัฒนาเท่านั้น (Vite, Tailwind, etc.) |
node_modules & package-lock.json
Section titled “node_modules & package-lock.json”# สร้างโปรเจกต์ใหม่npm init -y # สร้าง package.json
# ติดตั้ง dependenciesnpm install # อ่าน package.json แล้วโหลด packages ทั้งหมด| ไฟล์/โฟลเดอร์ | คืออะไร | ต้อง commit ไหม? |
|---|---|---|
package.json | รายชื่อ packages | ต้อง commit |
package-lock.json | เวอร์ชันที่แน่นอนของ packages | ต้อง commit |
node_modules/ | โค้ดจริงของ packages ที่โหลดมา | ห้าม commit — ใส่ใน .gitignore |
npx — รัน package โดยไม่ต้องติดตั้ง
Section titled “npx — รัน package โดยไม่ต้องติดตั้ง”# สร้างโปรเจกต์ React ด้วย Vite (ไม่ต้อง install Vite ก่อน)npx create-vite@latest my-app
# รัน package ชั่วคราวnpx tailwindcss initAlternative Package Managers
Section titled “Alternative Package Managers”npm ไม่ใช่ตัวเดียว — มีตัวอื่นที่เร็วกว่า ถ้าเจอในโปรเจกต์ของทีม ก็ใช้วิธีเดียวกัน
| Package Manager | คำสั่งติดตั้ง | จุดเด่น |
|---|---|---|
| npm | มาพร้อม Node.js | มาตรฐาน, ใช้ง่าย |
| pnpm | npm install -g pnpm | เร็ว, ประหยัดเนื้อที่ |
| yarn | npm install -g yarn | เสถียร, ใช้กันมานาน |
| bun | bun.sh | เร็วมาก, runtime + package manager ในตัว |
# ทุกตัวใช้คำสั่งคล้ายกันnpm install react # npmpnpm add react # pnpmyarn add react # yarnbun add react # bunสรุป: คำสั่ง npm ที่ใช้บ่อย
Section titled “สรุป: คำสั่ง npm ที่ใช้บ่อย”| คำสั่ง | ทำอะไร |
|---|---|
npm init -y | สร้าง package.json ใหม่ |
npm install | ติดตั้ง packages ทั้งหมดจาก package.json |
npm install <pkg> | ติดตั้ง package เพิ่ม |
npm install -D <pkg> | ติดตั้งเป็น dev dependency |
npm uninstall <pkg> | ลบ package |
npm run dev | รัน script “dev” (เปิด dev server) |
npm run build | รัน script “build” (สร้างไฟล์ production) |
npx <cmd> | รันคำสั่งจาก package โดยไม่ต้องติดตั้ง |
ลองทำเอง
Section titled “ลองทำเอง”-
Counter App — สร้างปุ่ม +/- ที่เปลี่ยนตัวเลข พร้อมปุ่ม Reset
-
Todo List — เพิ่ม, ลบ, toggle done ใช้ state pattern
-
Dark Mode — toggle theme แล้วบันทึกใน localStorage
-
รวมทั้งหมด — Todo List + Dark Mode + localStorage เป็นโปรเจกต์เดียว