Chào mừng trở lại với series “React Roadmap”! Trên hành trình khám phá React, chúng ta đã cùng nhau tìm hiểu về lộ trình học React tổng thể, nắm bắt được React là gì và vì sao nó lại phổ biến đến vậy, cũng như sự chuyển mình quan trọng từ Class Components sang Functional Components. Hôm nay, chúng ta sẽ đào sâu vào một khía cạnh cốt lõi, là “ngôn ngữ” mà bạn sẽ dùng hàng ngày khi làm việc với React: JSX.
Lần đầu tiếp xúc với JSX, bạn có thể cảm thấy hơi lạ lẫm. Trông nó giống HTML, nhưng lại nằm gọn trong file JavaScript? Chính xác! JSX là sự kết hợp mạnh mẽ, cho phép chúng ta mô tả giao diện người dùng (UI) ngay trong mã JavaScript. Hãy cùng tìm hiểu sâu hơn về cú pháp đặc biệt này nhé!
Mục lục
JSX Là Gì?
Nói một cách đơn giản nhất, JSX là một cú pháp mở rộng (syntax extension) cho JavaScript. Nó được tạo ra bởi Facebook (đội ngũ đứng sau React) để giúp các lập trình viên dễ dàng viết mã mô tả giao diện người dùng trong React.
Quan trọng cần nhớ: JSX không phải là HTML. Mặc dù cú pháp của nó rất giống HTML, nhưng nó không phải là ngôn ngữ đánh dấu (markup language) được trình duyệt hiểu trực tiếp. Trình duyệt chỉ hiểu HTML, CSS và JavaScript thuần túy.
Khi bạn viết mã JSX, nó sẽ được các công cụ như Babel hoặc TypeScript “dịch” (transpile) thành các lệnh gọi hàm JavaScript thuần túy mà React có thể hiểu và sử dụng để xây dựng cây UI ảo (Virtual DOM).
// Đây là code JSX:
const element = <h1>Xin chào, thế giới!</h1>;
Đoạn mã JSX trên sẽ được dịch thành lệnh gọi hàm JavaScript như sau:
// Đây là code JavaScript thuần túy tương đương:
const element = React.createElement(
'h1', // Loại phần tử
null, // Thuộc tính (attributes)
'Xin chào, thế giới!' // Nội dung (children)
);
Có thể thấy, việc viết JSX dễ đọc và trực quan hơn rất nhiều so với việc gọi `React.createElement` một cách thủ công, đặc biệt là khi cấu trúc UI trở nên phức tạp.
Tại Sao React Lại Chọn JSX?
Câu hỏi này thường xuất hiện khi mới bắt đầu học React. Tại sao không dùng các template engine truyền thống như Handlebars, Pug, hay chỉ đơn giản là viết HTML trong các chuỗi (string) JavaScript?
Lý do chính đằng sau việc sử dụng JSX nằm ở triết lý thiết kế của React: UI logic và rendering logic thường gắn liền với nhau. Thay vì tách biệt HTML, CSS và JavaScript thành các file riêng biệt (như cách làm truyền thống), React khuyến khích việc nhóm chúng lại theo từng component.
JSX cho phép bạn viết code mô tả cấu trúc UI (giống HTML) cùng với logic xử lý UI (JavaScript) ngay trong một file component. Điều này mang lại nhiều lợi ích:
- Đọc hiểu tốt hơn (Readability): Việc cấu trúc UI được thể hiện trực quan ngay trong code JavaScript giúp bạn dễ dàng hình dung và theo dõi luồng dữ liệu.
- Dễ viết và bảo trì (Easier to Write & Maintain): Khi logic UI (như render có điều kiện, lặp danh sách) nằm cạnh cấu trúc UI, việc thay đổi hoặc sửa lỗi trở nên nhanh chóng và ít nhầm lẫn hơn. Bạn không phải chuyển đổi qua lại giữa các file khác nhau.
- Biểu cảm (Expressiveness): Bạn có thể sử dụng toàn bộ sức mạnh của JavaScript (biến, hàm, vòng lặp, điều kiện) ngay trong cấu trúc UI của mình.
- An toàn (Safety): React (thông qua JSX) tự động escape các giá trị được nhúng vào DOM. Điều này giúp ngăn chặn các cuộc tấn công XSS (Cross-Site Scripting) phổ biến. Mọi thứ được render dưới dạng text, trừ khi bạn explicit chỉ định khác (thường là không nên làm trừ khi thực sự hiểu rõ).
- Tối ưu hóa (Optimization): Mặc dù bạn viết code trông giống HTML, React sử dụng thông tin này để xây dựng Virtual DOM và thực hiện các cập nhật hiệu quả lên DOM thật.
Ban đầu có thể cảm thấy lạ, nhưng khi quen với việc đặt logic và cấu trúc UI cùng nhau trong component, bạn sẽ thấy nó rất hiệu quả.
Các Cú Pháp Cơ Bản Của JSX
Hãy đi vào các cú pháp phổ biến nhất của JSX mà bạn sẽ gặp hàng ngày.
Nhúng Biểu Thức JavaScript Với {}
Đây là một trong những tính năng mạnh mẽ nhất của JSX. Bạn có thể nhúng bất kỳ biểu thức JavaScript hợp lệ nào vào trong JSX bằng cách sử dụng cặp dấu ngoặc nhọn `{}`. Kết quả của biểu thức này sẽ được hiển thị trong DOM.
const name = 'Developer EVOTEK';
const greeting = <h1>Xin chào, {name}!</h1>; // Nhúng biến 'name'
const sum = 10 + 5;
const result = <p>Kết quả của 10 + 5 là: {sum}.</p>; // Nhúng biểu thức toán học
function formatUser(user) {
return user.firstName + ' ' + user.lastName;
}
const user = { firstName: 'Nguyễn', lastName: 'Văn A' };
const userElement = (
<h2>Xin chào, {formatUser(user)}!</h2> // Nhúng kết quả của hàm
);
Bạn có thể nhúng biến, hằng số, kết quả của hàm, biểu thức toán học, thậm chí là các đối tượng (như styles) hoặc mảng (để render danh sách, sẽ nói sau) bên trong `{}`.
Thuộc Tính (Attributes)
Trong JSX, bạn có thể thêm các thuộc tính cho các phần tử giống như trong HTML.
const link = <a href="https://evotek.vn" target="_blank">Visit EVOTEK</a>;
const image = <img src="/logo.png" alt="EVOTEK Logo" />;
Tuy nhiên, có một vài điểm khác biệt quan trọng so với HTML truyền thống:
- Sử dụng `camelCase` cho tên thuộc tính: Hầu hết các thuộc tính trong JSX đều sử dụng cú pháp camelCase thay vì snake-case (viết thường, cách nhau bởi dấu gạch ngang) như trong HTML. Ví dụ: `tabIndex` thay vì `tabindex`, `readOnly` thay vì `readonly`.
- Một số thuộc tính được đổi tên: Để tránh xung đột với các từ khóa dành riêng trong JavaScript, một số thuộc tính phổ biến trong HTML đã được đổi tên trong JSX. Hai ví dụ điển hình là `class` trở thành `className` và `for` (trong thẻ `<label>`) trở thành `htmlFor`.
- Giá trị thuộc tính: Nếu giá trị là chuỗi, bạn dùng dấu nháy kép `””` hoặc nháy đơn `”` như HTML. Nếu giá trị là biểu thức JavaScript (biến, số, boolean, object, mảng), bạn phải sử dụng cặp dấu ngoặc nhọn `{}`.
const buttonType = 'submit';
const isEnabled = true;
const button = <button type={buttonType} disabled={!isEnabled}>Gửi</button>;
const myStyles = {
color: 'blue',
fontSize: '18px' // camelCase cho thuộc tính CSS
};
const styledDiv = <div style={myStyles}>Đây là div được style.</div>;
Render Có Điều Kiện (Conditional Rendering)
Nhờ khả năng nhúng JavaScript, bạn có thể dễ dàng render các phần tử một cách có điều kiện.
- Sử dụng toán tử ba ngôi (`? :`): Đây là cách phổ biến và gọn gàng nhất để render một trong hai trường hợp.
const isLoggedIn = true;
const greeting = isLoggedIn ? (
<h2>Chào mừng trở lại!</h2>
) : (
<h2>Vui lòng đăng nhập.</h2>
);
// Bạn có thể render 'greeting' trong component của mình
- Sử dụng toán tử logic `&&`: Nếu bạn chỉ muốn render một phần tử khi điều kiện đúng và không render gì cả khi điều kiện sai, toán tử `&&` rất hữu ích.
const itemsInCart = 5;
const cartStatus = (
<div>
<p>Giỏ hàng của bạn:</p>
{itemsInCart > 0 &&
<p>Bạn có {itemsInCart} mặt hàng trong giỏ.</p> // Chỉ hiển thị khi itemsInCart > 0
}
</div>
);
React sẽ render phần tử sau `&&` nếu biểu thức bên trái là `true`. Nếu là `false` (hoặc 0, null, undefined, …), React sẽ bỏ qua và không render gì.
Render Danh Sách (Rendering Lists)
Để render một danh sách các phần tử từ một mảng dữ liệu, bạn thường sử dụng phương thức `map()` của JavaScript.
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number, index) =>
<li key={index}> {/* Quan trọng: Mỗi phần tử trong danh sách cần có key */}
{number}
</li>
);
// Sau đó, bạn render danh sách này bên trong một container như <ul> hoặc <ol>
const numberList = (
<ul>
{listItems}
</ul>
);
// Hoặc viết gọn hơn:
const numberListShort = (
<ul>
{numbers.map((number, index) =>
<li key={index}>
{number}
</li>
)}
</ul>
);
Lưu ý quan trọng về `key`: Khi render danh sách các phần tử trong JSX, bạn luôn luôn cần cung cấp một thuộc tính `key` duy nhất cho mỗi phần tử trong danh sách (ví dụ: trên thẻ `<li>`). `key` giúp React xác định được phần tử nào đã thay đổi, thêm mới, hoặc bị xóa trong danh sách. Điều này cực kỳ quan trọng cho hiệu suất và sự ổn định khi React cần cập nhật giao diện.
- `key` nên là một giá trị duy nhất và ổn định cho mỗi item trong danh sách (ví dụ: ID từ cơ sở dữ liệu).
- Việc sử dụng `index` của mảng làm `key` chỉ nên dùng khi:
- Danh sách không thay đổi (không thêm, xóa, sắp xếp lại).
- Danh sách không có các item có ID cố định.
Nếu không, việc sử dụng `index` có thể gây ra lỗi không mong muốn hoặc làm giảm hiệu suất.
Trả Về Nhiều Phần Tử: React Fragments
Một quy tắc trong JSX là một component hoặc một hàm trả về JSX chỉ có thể trả về một phần tử gốc duy nhất.
// SAI - Không được trả về nhiều phần tử gốc
function MyComponent() {
return (
<h2>Tiêu đề</h2>
<p>Nội dung</p>
);
}
// ĐÚNG - Bọc trong một phần tử gốc
function MyComponentCorrect() {
return (
<div>
<h2>Tiêu đề</h2>
<p>Nội dung</p>
</div>
);
}
Tuy nhiên, việc thêm một thẻ `<div>` thừa thãi chỉ để đáp ứng quy tắc này có thể ảnh hưởng đến cấu trúc HTML cuối cùng hoặc CSS. Để giải quyết vấn đề này, React cung cấp Fragments.
Fragments cho phép nhóm nhiều phần tử con lại với nhau mà không thêm nút DOM thừa vào cây HTML.
// Sử dụng Fragment đầy đủ
import React from 'react';
function MyComponentWithFragment() {
return (
<React.Fragment>
<h2>Tiêu đề</h2>
<p>Nội dung</p>
</React.Fragment>
);
}
// Cú pháp ngắn gọn (phổ biến hơn)
function MyComponentShortFragment() {
return (
<> {/* Cú pháp ngắn gọn cho Fragment */}
<h2>Tiêu đề</h2>
<p>Nội dung</p>
</>
);
}
Cú pháp ngắn gọn (`<>…</>`) rất tiện lợi, nhưng không thể nhận các thuộc tính (như `key` khi dùng trong danh sách). Trong trường hợp cần `key`, bạn phải dùng cú pháp đầy đủ `<React.Fragment key=”…”>…</React.Fragment>`.
Styling Trong JSX
Có nhiều cách để áp dụng style trong React/JSX:
- Sử dụng `className`: Đây là cách phổ biến nhất, tương tự như sử dụng thuộc tính `class` trong HTML, nhưng dùng tên `className`. Bạn viết CSS trong file CSS riêng (hoặc sử dụng CSS Modules, Styled Components, …) và gán tên class vào thuộc tính `className`.
import './MyComponent.css'; // Import file CSS
function MyComponent() {
return (
<div className="container"> {/* Sử dụng tên class CSS */}
<p className="text-primary">Văn bản màu xanh.</p>
</div>
);
}
- Inline Styles: Bạn có thể viết style trực tiếp trong JSX bằng cách truyền một đối tượng JavaScript vào thuộc tính `style`.
function MyStyledComponent() {
const myStyles = {
color: 'red', // Các thuộc tính CSS được viết dưới dạng camelCase
fontSize: '20px',
fontWeight: 'bold'
};
return (
<p style={myStyles}>Đoạn văn bản được style inline.</p>
);
}
Lưu ý rằng các thuộc tính CSS trong đối tượng style phải được viết bằng `camelCase` thay vì `snake-case` (ví dụ: `fontSize` thay vì `font-size`). Giá trị có thể là chuỗi hoặc số (React sẽ tự thêm ‘px’ cho các thuộc tính kích thước).
Điểm Khác Biệt Chính Giữa JSX Và HTML
Để giúp bạn dễ dàng chuyển đổi từ HTML sang JSX, đây là bảng tóm tắt một số điểm khác biệt chính:
Tính năng | HTML | JSX | Ghi chú |
---|---|---|---|
Thuộc tính Class | class |
className |
Tránh xung đột với từ khóa class trong JS |
Thuộc tính For (label) | for |
htmlFor |
Tránh xung đột với từ khóa for trong JS |
Sự kiện (Events) | onclick="myFunction()" |
onClick={myFunction} onClick={() => doSomething()} |
Sử dụng camelCase (onClick , onChange , …). Giá trị là một hàm JS. |
Inline Style | style="color: red; font-size: 16px;" |
style={{ color: 'red', fontSize: '16px' }} |
Giá trị là một object JS. Tên thuộc tính CSS dùng camelCase. |
Comment | <!-- comment --> |
{/* comment */} |
Sử dụng cú pháp comment của JS bên trong dấu {} . |
Giá trị Boolean cho thuộc tính | Chỉ cần tên thuộc tính (ví dụ: disabled ) |
disabled={true} hoặc chỉ disabled disabled={false} (thuộc tính bị bỏ qua) |
Sử dụng `{true}` hoặc chỉ tên thuộc tính để kích hoạt. `{false}` để bỏ qua. |
Tự đóng thẻ | Một số thẻ (<img> , <input> ) |
Tất cả các thẻ không có nội dung con (<img /> , <input /> , <br /> ) phải được đóng lại bằng /> |
Những Lưu Ý Thường Gặp Khi Sử Dụng JSX
- Chỉ một phần tử gốc: Như đã đề cập, mỗi khối JSX cần được bọc bởi một phần tử cha duy nhất (hoặc dùng Fragment).
- Sử dụng `className` thay vì `class`.
- Sử dụng `htmlFor` thay vì `for`.
- camelCase cho thuộc tính HTML/DOM và thuộc tính CSS inline.
- Luôn đóng các thẻ tự đóng (`<img />`, `<input />`, …).
- Cung cấp `key` khi render danh sách các phần tử.
- Chỉ đặt biểu thức JavaScript trong `{}`: Không đặt các câu lệnh (statements) như `if/else` hay `for loop` trực tiếp vào trong `{}`. Thay vào đó, sử dụng toán tử ba ngôi, `&&`, hoặc thực hiện logic đó bên ngoài JSX và nhúng kết quả vào.
- Comment trong JSX: Sử dụng cú pháp `{/* comment */}`.
Kết Luận
JSX là một phần không thể thiếu của hệ sinh thái React hiện đại. Mặc dù ban đầu có thể cảm thấy hơi khác biệt so với cách tiếp cận truyền thống, nhưng sự kết hợp giữa JavaScript và cấu trúc UI mà JSX mang lại là vô cùng mạnh mẽ.
Nó giúp code của bạn trở nên rõ ràng, dễ bảo trì hơn, và cho phép bạn tận dụng toàn bộ sức mạnh của JavaScript để xây dựng giao diện người dùng phức tạp một cách linh hoạt. Việc hiểu vững JSX là bước đệm quan trọng để bạn có thể viết các component React hiệu quả và đẹp mắt.
Sau khi đã nắm vững JSX, bạn đã sẵn sàng để tìm hiểu cách các component giao tiếp với nhau, quản lý dữ liệu nội bộ (state), và xử lý các tương tác của người dùng – những chủ đề mà chúng ta sẽ khám phá trong các bài viết tiếp theo của series “React Roadmap”.
Chúc bạn học tốt và hẹn gặp lại trong bài viết tiếp theo!