JSX & Function Component
JSX (JavaScript XML) là cú pháp mở rộng cho phép viết HTML bên trong JavaScript. React biên dịch JSX thành các lời gọi React.createElement().
Function Component là hàm JavaScript thông thường nhận props vào và trả về JSX. Đây là cách xây dựng component phổ biến nhất trong React hiện đại.
// Cú pháp cơ bản của một Function Component function TenComponent() { // Logic JavaScript ở đây return ( <div> <h1>Xin chào React!</h1> </div> ); } export default TenComponent;
Trong dự án
Tất cả component đều là Function Component: App(), Navbar(), HomePage(), DanhSachSanPham(), Footer()…
// App.tsx – Component gốc của ứng dụng function App() { return ( <div className="App"> <BrowserRouter> <Navbar ... /> <Routes> ... </Routes> <Footer /> </BrowserRouter> </div> ); } export default App;
Lưu ý JSX
Dùng className thay class, htmlFor thay for. Mọi thành phần JSX phải có một thẻ bọc ngoài duy nhất (hoặc dùng <>...</>).
Props & TypeScript Interface
Props (properties) là cơ chế truyền dữ liệu từ component cha xuống component con. Props là read-only – component con không được tự thay đổi props nhận vào.
Kết hợp với TypeScript Interface, ta định nghĩa rõ kiểu dữ liệu cho từng prop, giúp IDE báo lỗi sớm và code dễ bảo trì hơn.
// Bước 1: Định nghĩa Interface (kiểu Props) interface NavbarProps { tuKhoaTimKiem: string; setTuKhoaTimKiem: (tuKhoa: string) => void; } // Bước 2: Nhận Props vào component (destructuring) function Navbar({ tuKhoaTimKiem, setTuKhoaTimKiem }: NavbarProps) { return (<nav>...</nav>); } // Bước 3: Truyền Props từ component cha <Navbar tuKhoaTimKiem={tuKhoaTimKiem} setTuKhoaTimKiem={setTuKhoaTimKiem} />
Trong dự án
NavbarProps, HomePageProps, DanhSachSanPhamProps, PaginationInterface đều là TypeScript interface định nghĩa props cho component tương ứng.
| Loại Prop | Ví dụ trong dự án | Mô tả |
|---|---|---|
string | tuKhoaTimKiem: string | Từ khoá tìm kiếm |
number | maTheLoai: number | Mã thể loại sách |
function | setTuKhoaTimKiem: (s: string)=>void | Hàm callback cập nhật state ở cha |
any | phanTrang: any | Hàm phân trang (nên dùng kiểu cụ thể hơn) |
Hook: useState
useState là hook cho phép component lưu trữ và cập nhật dữ liệu. Khi state thay đổi, React tự động re-render lại component đó.
const [giaTriHienTai, hamCapNhat] = useState(giaTri_Ban_Dau);
// App.tsx – State từ khoá tìm kiếm toàn cục const [tuKhoaTimKiem, setTuKhoaTimKiem] = useState(''); // navbar.tsx – State tạm thời trong ô input const [tuKhoaTamThoi, setTuKhoaTamThoi] = useState(''); // DanhSachSanPham.tsx – Nhiều state quản lý danh sách sách const [danhSachQuyenSach, setDanhSachQuyenSach] = useState<SachModel[]>([]); const [dangTaiDuLieu, setDangTaiDuLieu] = useState<boolean>(true); const [baoLoi, setBaoLoi] = useState(null); const [trangHienTai, setTrangHienTai] = useState(1); const [tongSoTrang, setTongSoTrang] = useState(0);
Quy tắc quan trọng
Không bao giờ trực tiếp gán state = value. Luôn dùng hàm setter như setTrangHienTai(2) để React biết cần re-render.
Pattern: Nâng State Lên (Lifting State Up)
Trong dự án, tuKhoaTimKiem được khai báo ở App rồi truyền cả giá trị lẫn hàm setter xuống Navbar và HomePage. Đây là cách chia sẻ state giữa các component anh em (siblings).
Hook: useEffect
useEffect xử lý các side effect: gọi API, đăng ký event listener, thay đổi DOM… Nó chạy sau khi component render xong.
useEffect(() => { // Code side effect ở đây }, [dependency1, dependency2]); // Dependency Array
| Dependency Array | Khi nào chạy? |
|---|---|
| Không có (bỏ qua) | Sau mỗi lần render |
[] (rỗng) | Chỉ một lần sau khi mount |
[a, b] | Khi a hoặc b thay đổi |
Trong dự án – DanhSachSanPham.tsx
Hai useEffect được dùng: (1) Reset trang về 1 khi từ khoá đổi, (2) Gọi API tìm kiếm khi trang/từ khoá/thể loại thay đổi.
// useEffect 1: Reset trang khi đổi từ khóa useEffect(() => { setTrangHienTai(1); }, [tuKhoaTimKiem]); // Chạy khi tuKhoaTimKiem thay đổi // useEffect 2: Gọi API khi trang / từ khóa / thể loại thay đổi useEffect(() => { timKiemSach(tuKhoaTimKiem, maTheLoai, trangHienTai - 1) .then(kq => { setDanhSachQuyenSach(kq.ketQua); setTongSoTrang(kq.tongSoTrang); setDangTaiDuLieu(false); }) .catch(error => { setBaoLoi(error.message); setDangTaiDuLieu(false); }); }, [trangHienTai, tuKhoaTimKiem, maTheLoai]);
Hook: useParams
useParams (từ react-router-dom) trả về object chứa các tham số động trên URL, được khai báo bằng dấu : trong Route.
// Route khai báo tham số :maTheLoai <Route path='/:maTheLoai' element={<HomePage ... />} /> // HomePage.tsx – đọc tham số từ URL const { maTheLoai } = useParams(); // URL: /3 → maTheLoai = "3" (string) let maTheLoaiNumber = parseInt(maTheLoai + ''); if (Number.isNaN(maTheLoaiNumber)) maTheLoaiNumber = 0;
Lưu ý
useParams luôn trả về string. Dự án dùng parseInt để chuyển sang number và validate với Number.isNaN() tránh lỗi khi URL không hợp lệ.
React Router DOM
Thư viện điều hướng cho React SPA (Single Page Application). Thay vì tải lại trang, router cập nhật URL và render component tương ứng mà không reload.
BrowserRouter
Bọc toàn bộ app. Kích hoạt tính năng routing cho các component con bên trong.
Routes + Route
Định nghĩa bảng ánh xạ URL → Component. Routes chọn Route khớp nhất.
Link
Thay thế thẻ <a>. Điều hướng mà không reload trang, giữ SPA.
useParams
Hook đọc tham số động từ URL, ví dụ /:maTheLoai.
// App.tsx – Cấu trúc Routing của dự án <BrowserRouter> <Navbar ... /> <Routes> <Route path='/' element={<HomePage ... />} /> <Route path='/:maTheLoai' element={<HomePage ... />} /> <Route path='/about' element={<About />} /> </Routes> <Footer /> </BrowserRouter> // navbar.tsx – Dùng Link thay <a href> <Link to="/">Trang chủ</Link> <Link to="/about">Về Bookstore</Link> <Link to="/1">Tiểu thuyết</Link>
Conditional Rendering
Hiển thị UI khác nhau tuỳ theo điều kiện, tương tự câu lệnh if nhưng áp dụng trong JSX.
// DanhSachSanPham.tsx – 3 trường hợp // 1. Đang tải dữ liệu if (dangTaiDuLieu) { return (<div><h1>Đang tải dữ liệu</h1></div>); } // 2. Có lỗi xảy ra if (baoLoi) { return (<div><h1>Gặp lỗi: {baoLoi}</h1></div>); } // 3. Không có sách if (danhSachQuyenSach.length === 0) { return (<div><h1>Sách bạn cần chúng tôi không bán</h1></div>); } // 4. Hiển thị danh sách bình thường return (<div>...danh sach sach...</div>);
Toán tử && trong JSX
Dùng {dieu_kien && <Component />} để render có điều kiện trong JSX. Dùng toán tử ternary a ? b : c khi cần trả về 2 lựa chọn.
Render Danh Sách với Array.map()
Để render một danh sách các phần tử, dùng .map() để biến mỗi phần tử data thành một component JSX. Mỗi phần tử phải có prop key duy nhất.
// DanhSachSanPham.tsx – Render danh sách sách { danhSachQuyenSach.map(sach => ( <SachProps key={sach.maSach} // KEY duy nhất, bắt buộc! sach={sach} /> )) } // pagination.tsx – Render danh sách số trang { danhSachTrang.map(trang => ( <li key={trang} className={"page-item " + (trang === props.trangHienTai ? "active" : "")} onClick={() => props.phanTrang(trang)} > <button className="page-link">{trang}</button> </li> )) }
Tại sao cần prop key?
React dùng key để nhận biết phần tử nào cần cập nhật, thêm, hoặc xoá khi danh sách thay đổi, giúp tối ưu hiệu suất. Không dùng index mảng làm key nếu danh sách có thể thay đổi thứ tự.
Async / Await & Gọi API
Giao tiếp với backend thông qua HTTP. Dự án dùng fetch kết hợp async/await trong tầng API riêng biệt (src/api/), rồi gọi từ component qua useEffect.
// api/Request.tsx – Hàm fetch tầng thấp nhất export async function my_request(duongDan: string) { const response = await fetch(duongDan); if (!response.ok) throw new Error('Lỗi khi gọi API'); return response.json(); } // api/SachAPI.ts – Hàm nghiệp vụ async function laySach(duongDan: string): Promise<KetQuaInterface> { const response = await my_request(duongDan); // Xử lý dữ liệu, map sang SachModel... return { ketQua, tongSoTrang, tongSoSach }; } // Component – Gọi API và xử lý kết quả timKiemSach(tuKhoaTimKiem, maTheLoai, trangHienTai - 1) .then(kq => { // Thành công setDanhSachQuyenSach(kq.ketQua); setDangTaiDuLieu(false); }) .catch(error => { // Thất bại setBaoLoi(error.message); setDangTaiDuLieu(false); });
async/await vs .then/.catch
Cả hai đều xử lý Promise. async/await giúp code đọc theo chiều dọc dễ hơn. .then/.catch dùng khi không cần await kết quả ngay (ví dụ gọi trong useEffect không dùng async trực tiếp).
React.FC & TypeScript trong Component
React.FC (Function Component) là kiểu TypeScript generic giúp typing props rõ ràng hơn. Dự án dùng cả hai cách khai báo component.
// Cách 1: Function bình thường + Interface (phần lớn dự án dùng) function DanhSachSanPham({ tuKhoaTimKiem, maTheLoai }: DanhSachSanPhamProps) { ... } // Cách 2: React.FC Generic (pagination.tsx dùng) export const Pagination: React.FC<PaginationInterface> = (props) => { ... }
TypeScript cũng được dùng để định nghĩa Model cho dữ liệu từ API:
// model/SachModel.ts – Kiểu dữ liệu cho một cuốn sách interface SachModel { maSach: number; tenSach: string; tenTacGia?: string; // ? = optional giaNiemYet?: number; giaBan?: number; moTa?: string; }
Luồng Dữ Liệu Tổng Thể
Sơ đồ cách dữ liệu và sự kiện đi qua các tầng của dự án:
╔══════════════════════════════════════════════════════════╗
║ App.tsx ║
║ state: tuKhoaTimKiem ║
╠══════════════╦═══════════════════════════════════════════╣
║ Navbar ║ Routes ║
║ (props: từ ║ / → HomePage → DanhSachSanPham ║
║ khóa + set) ║ /:maThe → HomePage → DanhSachSanPham ║
║ ║ /about → About ║
╚══════════════╩═══════════════════════════════════════════╝
User gõ từ khóa:
Navbar → setTuKhoaTamThoi (local state)
Click Search → setTuKhoaTimKiem (App state)
App re-render → truyền tuKhoaTimKiem mới xuống HomePage
DanhSachSanPham → useEffect kích hoạt → gọi API → re-render list
User click thể loại (Link to="/1"):
URL đổi → Route /:maTheLoai khớp
HomePage → useParams() → maTheLoai = "1"
DanhSachSanPham nhận maTheLoai=1 → useEffect → gọi API lọc
One-Way Data Flow
React luôn truyền dữ liệu một chiều: từ cha xuống con qua props. Để con cập nhật cha, con gọi hàm callback được cha truyền xuống (ví dụ setTuKhoaTimKiem).
useState + useEffect
Kết hợp cốt lõi: state thay đổi → useEffect chạy → gọi API → setState lại → re-render.
URL = State
Thể loại sách được lưu trên URL (/1, /2…), đọc qua useParams. URL vừa là navigation vừa là state.
Tách Tầng API
Dự án tách logic gọi API vào src/api/ riêng, giúp component sạch và dễ test.
TypeScript Interfaces
Mọi Props và Model đều có Interface, đảm bảo type safety và hiển thị gợi ý trong IDE.