JSX và component đầu tiên
Ở module 1 bạn đã dựng dự án Vite và tập nhìn giao diện theo khối component. Giờ là lúc viết component thật. Toàn bộ module 2 xoay quanh một ý: bạn mô tả giao diện bằng dữ liệu và cấu trúc, React lo việc cập nhật DOM. Bài này trang bị cú pháp để làm được điều đó.
Sau bài học này, bạn sẽ:
- Viết được JSX và phân biệt rõ nó khác HTML ở đâu.
- Nhúng biểu thức JavaScript vào JSX bằng cặp ngoặc nhọn.
- Tách một màn hình thành component cha và component con.
- Nhận ra 4 lỗi JSX mà người mới gần như ai cũng mắc.
JSX trông giống HTML nhưng không phải HTML
Mở src/App.jsx trong dự án Vite, bạn thấy một hàm trả về thứ trông y như HTML. Đó là JSX, viết tắt của JavaScript XML. Trình biên dịch của Vite sẽ chuyển JSX thành lời gọi React.createElement(...) trước khi trình duyệt nhìn thấy nó. Nghĩ đơn giản: JSX là cách viết giao diện bằng cú pháp quen thuộc, nhưng chạy bên trong JavaScript.
function App() {
return <h1>Chào mừng đến với ứng dụng quản lý công việc</h1>;
}
Vì nằm trong JavaScript, JSX phải nhường một số từ khóa cho ngôn ngữ chủ. Bảng dưới là những chỗ bạn sẽ gặp ngay trong buổi đầu:
| HTML quen thuộc | Viết trong JSX | Vì sao |
|---|---|---|
class="card" |
className="card" |
class là từ khóa của JavaScript |
for="task" |
htmlFor="task" |
for cũng là từ khóa |
<input type="text"> |
<input type="text" /> |
Mọi thẻ trong JSX phải đóng |
style="color: red" |
style=_____ |
style nhận một object |
| `` | {/* ghi chú */} |
Bình luận nằm trong biểu thức |
Note: JSX bắt buộc trả về một phần tử gốc duy nhất. Muốn trả về nhiều phần tử ngang cấp, bọc chúng trong
<div>hoặc dùng Fragment viết tắt<>...</>.
Bài tập 1. Đoạn JSX sau có 4 lỗi. Tìm và sửa.
function Card() {
return (
<div class="card">
<label for="task">Tên việc</label>
<input type="text">
<img src="logo.png">
</div>
);
}
Xem đáp án
Bốn lỗi là class, for, và hai thẻ chưa tự đóng. Bản đúng:
function Card() {
return (
<div className="card">
<label htmlFor="task">Tên việc</label>
<input type="text" />
<img src="logo.png" />
</div>
);
}
Nhúng JavaScript vào JSX bằng ngoặc nhọn
Đây là phần làm JSX mạnh hơn HTML: bất kỳ biểu thức JavaScript nào đặt trong {} đều được tính giá trị và chèn vào giao diện.
function TaskSummary() {
const ten = 'Lan';
const tongViec = 3;
const daXong = 1;
return (
<section>
<h2>Xin chào, {ten}</h2>
<p>Tổng cộng {tongViec} việc, đã xong {daXong}.</p>
<p>Còn lại: {tongViec - daXong} việc</p>
<p>Chữ hoa: {ten.toUpperCase()}</p>
</section>
);
}
Bạn nhúng được biến, phép tính, gọi hàm, truy cập thuộc tính. Nhưng chỉ biểu thức, không phải câu lệnh. Viết {if (daXong > 0) ...} là lỗi cú pháp ngay. Muốn rẽ nhánh, dùng toán tử ba ngôi hoặc &&:
{conLai > 0 ? <p>Còn {conLai} việc</p> : <p>Đã xong hết!</p>}
{laAdmin && <span className="badge">Quản trị</span>}
Bài tập 2. Cho const tasks = ['Học JSX', 'Làm bài tập', 'Xây app Vite']; và const done = 1;. Viết JSX hiển thị tổng số việc, và chỉ hiện dòng "Còn N việc" khi N lớn hơn 0.
Gợi ý và đáp án
Tính const conLai = tasks.length - done; trước, rồi dùng && để điều kiện hóa:
function Summary() {
const conLai = tasks.length - done;
return (
<div>
<p>Tổng cộng: {tasks.length} việc</p>
{conLai > 0 && <p>Còn {conLai} việc</p>}
</div>
);
}
Nhớ: nếu conLai là 0, biểu thức 0 && ... trả về 0 và React sẽ in ra số 0. Với trường hợp đó hãy dùng toán tử ba ngôi.
Component đầu tiên: một hàm trả về JSX
Component trong React chỉ là một hàm JavaScript. Nhận vào dữ liệu (bài sau sẽ nói về props), trả về JSX. Hai quy tắc bắt buộc:
- Tên component viết PascalCase, chữ cái đầu viết hoa:
TaskSummary,AppHeader. - Hàm phải trả về JSX hoặc
null.
Quy tắc chữ hoa không phải chuyện thẩm mỹ. React dựa vào chữ cái đầu để phân biệt: <header> là thẻ HTML, <Header /> là component của bạn. Viết <header /> khi định gọi component Header là lỗi rất hay gặp vì không báo lỗi, chỉ hiện sai.
Khi dùng component, bạn gọi nó như thẻ tự đóng: <AppHeader />, tương đương <AppHeader></AppHeader>.
Tách giao diện thành component cha con
Quy tắc thực dụng: một hàm dài quá khoảng 30 dòng, hoặc một khối giao diện có ranh giới rõ ràng (header, danh sách, footer), thì tách ra. Component App đóng vai cha, các component nhỏ là con.
flowchart LR
App["🧩<br/>App"] --> Header["🏷️<br/>AppHeader"]
App --> Summary["📊<br/>TaskSummary"]
App --> Footer["📌<br/>AppFooter"]
function AppHeader() {
return (
<header>
<h1>Quản lý công việc</h1>
</header>
);
}
function AppFooter() {
return <footer>Học React cùng Vite</footer>;
}
function App() {
return (
<div>
<AppHeader />
<TaskSummary />
<AppFooter />
</div>
);
}
export default App;
Vài dự án thật thường đặt mỗi component một file cho dễ đọc:
src/
components/
AppHeader.jsx
AppFooter.jsx
TaskSummary.jsx
App.jsx
main.jsx
Bài tập 3. Lấy đoạn JSX của bài tập 2 và tách thành TaskSummary (hiển thị số liệu) cùng App (bọc ngoài). Sau đó thêm AppHeader phía trên.
Hướng làm
Tạo ba hàm riêng, mỗi hàm trả về một khối JSX duy nhất, rồi App gọi cả ba bên trong một <div>. Kiểm tra bằng mắt: nếu trong App còn phép tính tasks.length - done thì bạn chưa tách xong, phần tính toán nên nằm trong TaskSummary.
Lỗi thường gặp khi viết JSX lần đầu
- Quên chữ hoa:
<appHeader />bị coi là thẻ HTML lạ và không render gì. Luôn dùng<AppHeader />. - Trả về hai phần tử gốc: bọc trong
<>...</>thay vì thêmdivvô nghĩa. - Dùng
class,for: đổi ngay sangclassName,htmlFor. React sẽ cảnh báo trong console. - Nhúng câu lệnh vào
{}: chỉ biểu thức mới hợp lệ.if,for,constphải nằm ngoài phầnreturn. - Quên
keykhi render mảng: chưa cần ở bài này, nhưng nhớ rằng bài 2.4 sẽ quay lại.
Mẹo nhỏ: giữ mỗi component trả về một khối giao diện có ý nghĩa, đừng tách vụn thành từng thẻ. Tách theo ranh giới giao diện, không tách theo số dòng.
Chốt lại và bước tiếp theo
Ba điều cần mang theo: JSX là HTML viết trong JavaScript với vài khác biệt cú pháp (className, htmlFor, thẻ tự đóng, một phần tử gốc); ngoặc nhọn {} là cửa để đưa biểu thức JavaScript vào giao diện; và component là hàm PascalCase trả về JSX, được gọi như thẻ tự đóng.
Hiện tại các component con của bạn chưa nhận được dữ liệu nào từ cha. Bài tiếp theo, Props và composition, sẽ giải quyết đúng điểm đó: truyền dữ liệu xuống component con để một TaskSummary dùng lại được cho nhiều bộ số liệu khác nhau.
JSX dùng thuộc tính _____ thay cho class của HTML.
Nhúng biểu thức JavaScript vào JSX bằng cặp {{c2::ngoặc nhọn {}}}.
Tên component React phải bắt đầu bằng _____.
JSX chỉ trả về _____, hoặc dùng Fragment <>...</>.
Trong JSX, thẻ không có nội dung như input phải _____.
Vì không viết được câu lệnh if trong JSX, ta dùng _____.
Front: JSX là gì? Back: Cú pháp giống HTML viết trong JavaScript, được biên dịch thành lời gọi React.createElement.
Front: Trong JSX, thay cho thuộc tính class của HTML ta viết gì? Back: className, vì class là từ khóa của JavaScript.
Front: Trong JSX, thay cho thuộc tính for của HTML ta viết gì? Back: htmlFor.
Front: Cách nhúng một biểu thức JavaScript vào JSX? Back: Đặt biểu thức trong cặp ngoặc nhọn {}, ví dụ {ten.toUpperCase()}.
Front: Component React là gì? Back: Một hàm JavaScript nhận dữ liệu và trả về JSX (hoặc null).
Front: Tên component phải viết theo quy ước nào? Back: PascalCase, chữ cái đầu viết hoa, ví dụ AppHeader.
Front: Vì sao JSX phải có một phần tử gốc duy nhất? Back: Vì hàm chỉ trả về được một giá trị; nhiều phần tử ngang cấp cần bọc trong div hoặc Fragment.
Front: Fragment viết tắt trong JSX trông thế nào? Back: <> ... </>, dùng khi cần bọc nhiều phần tử mà không thêm div.
Front: Viết bình luận trong JSX? Back: {/* nội dung bình luận */}.
Front: Inline style trong JSX viết thế nào? Back: Bằng object có thuộc tính camelCase: style={{ color: 'red' }}.
Front: Không dùng được câu lệnh if trong JSX, vậy render có điều kiện bằng gì? Back: Toán tử ba ngôi (a ? b : c) hoặc toán tử &&.
Front: Điều gì xảy ra khi viết
Front: Thẻ input trong JSX cần viết thế nào? Back: Phải tự đóng: .