Click chuột phải vào file HTML → "Open with Live Server". Browser sẽ tự reload khi bạn lưu file (Ctrl+S). Cực kỳ tiện khi làm việc với AI — generate code, save, thấy kết quả ngay.
2
Bài 6.2 — JavaScript Tương Tác
DOM Manipulation Patterns
javascript
// === CHỌN ELEMENTS ===
const btn = document.querySelector('#my-button'); // 1 element
const items = document.querySelectorAll('.item'); // NodeList
const input = document.getElementById('search-input'); // By ID
// === THÊM/XÓA CLASS ===
btn.classList.add('active');
btn.classList.remove('active');
btn.classList.toggle('active'); // Add nếu không có, remove nếu có
// === TẠO ELEMENT ĐỘNG ===
function createCard(item) {
const card = document.createElement('div');
card.className = 'card';
card.innerHTML = `
<h3>${item.title}</h3>
<p>${item.description}</p>
<button data-id="${item.id}" class="btn-delete">Xóa</button>
`;
return card;
}
// === EVENT DELEGATION (hiệu quả hơn addEventListener trên nhiều items) ===
document.querySelector('#list').addEventListener('click', (e) => {
const deleteBtn = e.target.closest('.btn-delete');
if (deleteBtn) {
const id = deleteBtn.dataset.id;
deleteItem(id);
}
});
// === FETCH API — gọi API ===
async function fetchTodos() {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/todos?_limit=10');
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const todos = await response.json();
renderTodos(todos);
} catch (error) {
showError('Không thể tải dữ liệu: ' + error.message);
}
}
function renderTodos(todos) {
const container = document.querySelector('#todos-container');
container.innerHTML = todos.map(todo => `
<div class="todo-item ${todo.completed ? 'completed' : ''}">
<input type="checkbox" ${todo.completed ? 'checked' : ''}
data-id="${todo.id}" />
<span>${todo.title}</span>
</div>
`).join('');
}
3
Bài 6.3 — React với AI
Setup React với Vite (nhanh nhất hiện tại)
bash
# Tạo React app với Vite
npm create vite@latest my-react-app -- --template react
cd my-react-app
npm install
npm run dev
# → Mở browser: http://localhost:5173
Component Pattern với AI
Khi dùng AI để tạo React components, luôn cung cấp:
text — Prompt mẫu cho React component
Tôi đang dùng React 18 với Vite. Viết component ProductCard:
Props: { id, name, price, image, rating, onAddToCart }
- Hiển thị ảnh sản phẩm, tên, giá (format VND), rating (stars)
- Button "Thêm vào giỏ" gọi onAddToCart(id)
- Loading state khi đang xử lý
- Dùng CSS modules (ProductCard.module.css)
- PropTypes validation
- Responsive: full width trên mobile, 1/3 trên desktop
Dùng Tailwind CSS (v3) viết component Notification Toast:
- 4 variants: success (green), error (red), warning (yellow), info (blue)
- Mỗi toast có: icon, title, message, close button
- Animation: slide in từ phải, tự đóng sau 5 giây
- Stack nhiều toasts theo chiều dọc, góc dưới phải màn hình
- React component, nhận prop: { type, title, message, onClose }
Cài extension Tailwind CSS IntelliSense trong VS Code. Khi gõ className, autocomplete hiện danh sách classes kèm preview. Copilot + IntelliSense = không cần nhớ bất kỳ class nào!
5
Bài 6.5 — Dự Án Thực Hành: Xây Dựng React Task Manager App
Chúng ta sẽ xây dựng một ứng dụng Task Manager bằng React + Tailwind CSS — có thêm/xóa/sửa task, lọc theo trạng thái, lưu localStorage, và responsive layout. Toàn bộ UI được viết với sự hỗ trợ của Copilot.
Bước 1 — Tạo Dự Án React Với Vite
bash — Tạo project Vite + React
# Tạo project mới với Vite (nhanh hơn Create React App 10x)
npm create vite@latest task-manager -- --template react
cd task-manager
# Cài dependencies mặc định
npm install
# Cài Tailwind CSS
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
# Cài thư viện bổ sung
npm install lucide-react uuid
# Chạy development server
npm run dev
# Mở trình duyệt tại: http://localhost:5173
bash — Build production và deploy lên Netlify/Vercel
# Build cho production
npm run build
# Output: thư mục dist/ chứa file tĩnh
# Preview bản build local
npm run preview
# Deploy lên Netlify (cách đơn giản nhất)
# 1. Cài Netlify CLI
npm install -g netlify-cli
# 2. Đăng nhập Netlify
netlify login
# 3. Deploy
netlify deploy --prod --dir=dist
# Nhận URL dạng: https://your-app.netlify.app
# Hoặc deploy lên Vercel
npm install -g vercel
vercel --prod
💡 Mẹo từ ThanhDoIT
AI rất giỏi Tailwind nhưng hay tạo ra class string quá dài. Dùng cn() helper (clsx + tailwind-merge) để merge classes conditionally — sạch và maintainable hơn.
Khi ask AI tạo React component, yêu cầu luôn: "Thêm loading state, error state và empty state cho component này." 3 states này hay bị quên.
useEffect có dependency array rỗng [] ≠ "chạy một lần". Nó chạy sau mỗi lần component mount. Hiểu rõ React lifecycle trước khi dùng AI generate hooks.
Bật React StrictMode (default trong Vite). Nó sẽ mount/unmount component 2 lần trong dev để phát hiện bugs — một số code AI generate sẽ fail test này.
6
Bài 6.6 — Design System & Component Library với AI
Thay vì code từng component riêng lẻ, senior developer build Design System — một bộ components tái sử dụng với style nhất quán. AI giúp bạn tạo Design System trong vài giờ thay vì vài tuần.
Chỉ dùng khi: computation thực sự expensive (lọc mảng lớn, sort, format phức tạp) hoặc function được pass vào component đã memo hóa
Không cần: tính toán đơn giản, string/number operations — overhead của memo lớn hơn lợi ích
AI thường thêm useMemo/useCallback vào mọi chỗ — hỏi lại: "Optimization này có thực sự cần không với data size này?"
Tôi có React component sau đang bị lag khi render danh sách 500+ items.
Hãy phân tích và optimize:
[paste component code vào đây]
Yêu cầu:
1. Identify các re-render không cần thiết
2. Thêm React.memo cho child components phù hợp
3. Thêm useMemo cho expensive computations
4. Thêm useCallback cho event handlers được pass xuống
5. Nếu cần, suggest dùng virtual list (react-window) cho list quá dài
6. Giải thích tại sao mỗi optimization cần thiết
Paste code thực tế + yêu cầu giải thích → Copilot không chỉ fix mà còn dạy bạn tại sao.
🎯 Thực Hành: Build Mini Design System
Tạo file design-tokens.css với color palette, spacing, typography của bạn
Hỏi Copilot: "Generate Button component React với 4 variants dùng CSS variables này"
Build thêm Input component với states: default, focus, error, disabled
Build Card component: header, body, footer slots
Tạo Modal component với backdrop, close button, keyboard ESC support
Export tất cả từ src/components/index.ts — có thể import như: import { Button, Input, Card } from '@/components'
Next.js 15 là framework React được dùng nhiều nhất trong production (Vercel, Airbnb, TikTok, Twitch đều dùng). Nó cho phép bạn viết cả frontend lẫn backend trong 1 project, với Server Components cho performance tuyệt vời và App Router cho routing linh hoạt.
App Router
Hệ thống routing của Next.js 13+. Mỗi folder trong app/ là một route. page.tsx = UI, layout.tsx = wrapper, loading.tsx = skeleton UI khi đang load.
Server Components
React components chạy trên server — không gửi JS xuống browser. Có thể fetch data trực tiếp, truy cập database, đọc file. Tốt cho SEO và performance.
Client Components
Components với 'use client' — chạy trên browser. Dùng khi cần: useState, useEffect, event handlers, browser APIs.
Server Actions
Functions với 'use server' — gọi từ client nhưng thực thi trên server. Thay thế REST API endpoint cho form submit, CRUD operations đơn giản.
Khởi Tạo Next.js 15 Project
bash — Create Next.js 15 app
npx create-next-app@latest my-next-app \
--typescript \
--tailwind \
--eslint \
--app \
--src-dir \
--import-alias "@/*"
cd my-next-app
npm run dev # http://localhost:3000
// Không có 'use client' → đây là Server Component
// Có thể dùng async/await trực tiếp!
import { db } from '@/lib/db' // Prisma hoặc bất kỳ DB client nào
interface Post {
id: string
title: string
excerpt: string
createdAt: Date
author: { name: string }
}
// Fetch data ngay trong component — không cần useEffect!
async function getPosts(): Promise<Post[]> {
return db.post.findMany({
include: { author: true },
orderBy: { createdAt: 'desc' },
take: 10,
})
}
export default async function BlogPage() {
const posts = await getPosts() // Chạy trên server, không gửi xuống browser
return (
<main className="max-w-4xl mx-auto px-4 py-8">
<h1 className="text-3xl font-bold mb-8">Blog</h1>
<div className="grid gap-6">
{posts.map(post => (
<article key={post.id} className="bg-white rounded-xl shadow p-6">
<h2 className="text-xl font-semibold mb-2">
<a href={`/blog/${post.id}`} className="hover:text-blue-600">
{post.title}
</a>
</h2>
<p className="text-gray-600 mb-3">{post.excerpt}</p>
<div className="text-sm text-gray-400">
{post.author.name} · {new Date(post.createdAt).toLocaleDateString('vi-VN')}
</div>
</article>
))}
</div>
</main>
)
}
// Metadata cho SEO — hoàn toàn server-side
export const metadata = {
title: 'Blog | MyApp',
description: 'Bài viết mới nhất về lập trình và AI',
}
Server Actions — Form Submit Không Cần API
typescript — Server Actions thay thế REST API
// app/actions/post.ts
'use server'
import { db } from '@/lib/db'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
import { z } from 'zod'
const CreatePostSchema = z.object({
title: z.string().min(3).max(200),
content: z.string().min(10),
})
export async function createPost(formData: FormData) {
// Validate với Zod
const validated = CreatePostSchema.safeParse({
title: formData.get('title'),
content: formData.get('content'),
})
if (!validated.success) {
return { error: validated.error.flatten().fieldErrors }
}
// Lưu vào DB trực tiếp — không qua REST API!
const post = await db.post.create({
data: {
...validated.data,
authorId: 'current-user-id', // Lấy từ session
}
})
// Invalidate cache để hiển thị bài mới
revalidatePath('/blog')
// Redirect sau khi tạo xong
redirect(`/blog/${post.id}`)
}
// -----------------------------------------------
// app/blog/new/page.tsx — Form dùng Server Action
'use client'
import { createPost } from '@/actions/post'
import { useFormState } from 'react-dom'
export default function NewPostPage() {
const [state, action] = useFormState(createPost, null)
return (
<form action={action} className="max-w-2xl mx-auto p-6 space-y-4">
<h1 className="text-2xl font-bold">Tạo bài viết mới</h1>
<div>
<label className="block text-sm font-medium mb-1">Tiêu đề</label>
<input name="title" className="w-full border rounded-lg px-3 py-2" required />
{state?.error?.title && (
<p className="text-red-500 text-sm mt-1">{state.error.title[0]}</p>
)}
</div>
<div>
<label className="block text-sm font-medium mb-1">Nội dung</label>
<textarea name="content" rows={8} className="w-full border rounded-lg px-3 py-2" />
</div>
<button type="submit" className="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700">
Đăng bài
</button>
</form>
)
}
Route Handler — API Endpoints Trong Next.js
typescript — app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
// GET /api/users
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url)
const page = parseInt(searchParams.get('page') ?? '1')
const limit = parseInt(searchParams.get('limit') ?? '20')
const [users, total] = await Promise.all([
db.user.findMany({
skip: (page - 1) * limit,
take: limit,
select: { id: true, name: true, email: true, createdAt: true },
}),
db.user.count(),
])
return NextResponse.json({ users, total, page, limit })
}
// POST /api/users
export async function POST(request: NextRequest) {
const body = await request.json()
// Validate, tạo user...
const user = await db.user.create({ data: body })
return NextResponse.json(user, { status: 201 })
}
React App (Vite)
Next.js App Router
Khi nào dùng
Client-only rendering
Server + Client rendering
Next.js khi cần SEO/performance
Cần backend riêng
API routes built-in
Next.js cho full-stack solo
SPA (Single Page App)
MPA + SPA hybrid
Next.js cho marketing sites
Vite: build cực nhanh
Turbopack: cũng rất nhanh
Gần tương đương
Setup đơn giản
Nhiều conventions hơn
Vite cho app thuần client
Deploy: Vercel/Netlify
Deploy: tốt nhất trên Vercel
Next.js nếu dùng Vercel
🤖
Dùng Copilot với Next.js
Next.js App Router có nhiều conventions phức tạp. Copilot rất hiệu quả ở đây. Hỏi: "Tạo Next.js 15 App Router page hiển thị danh sách products từ Prisma, có phân trang, với loading skeleton" — AI tự generate page.tsx, loading.tsx, và phân trang đúng conventions.