JSX & Rendering
JSX là gì?
JSX (JavaScript XML) là cú pháp mở rộng cho JavaScript, cho phép viết HTML-like code trực tiếp trong JavaScript. JSX được Babel biên dịch thành React.createElement() calls.
// JSX syntax
const element = <h1 className="title">Hello, World!</h1>;
// Biên dịch thành
const element = React.createElement(
'h1',
{ className: 'title' },
'Hello, World!'
);
Quy tắc JSX
// 1. Phải có một root element (hoặc Fragment)
function App() {
return (
<>
<h1>Title</h1>
<p>Content</p>
</>
);
}
// 2. Mọi tag phải được đóng
const img = <img src="photo.jpg" alt="photo" />;
const input = <input type="text" />;
// 3. className thay vì class
const div = <div className="container">Content</div>;
// 4. JavaScript expressions dùng {}
const name = "Alice";
const greeting = <h1>Hello, {name}!</h1>;
const result = <p>2 + 2 = {2 + 2}</p>;
// 5. Style dùng object
const styled = (
<div style={{ color: 'red', fontSize: '16px' }}>
Styled text
</div>
);
Virtual DOM
React sử dụng Virtual DOM để tối ưu hóa việc cập nhật UI thực.
┌─────────────────────────────────────────────────────────┐
│ STATE CHANGE │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ New Virtual DOM Tree │ │
│ └──────────────────────────────────────────────────┘ │
│ │ │
│ DIFFING (Reconciliation) │
│ │ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Old Virtual DOM Tree │ │
│ └──────────────────────────────────────────────────┘ │
│ │ │
│ Chỉ cập nhật phần thay đổi │
│ │ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Real DOM │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
Lợi ích:
- Giảm thiểu DOM manipulation (tốn kém)
- Batch updates - nhóm nhiều thay đổi lại
- Diffing algorithm tìm thay đổi tối thiểu
Conditional Rendering
function UserStatus({ isLoggedIn, user }) {
// 1. if-else với early return
if (!isLoggedIn) {
return <div>Please log in</div>;
}
// 2. Ternary operator
return (
<div>
{isLoggedIn ? (
<span>Welcome, {user.name}!</span>
) : (
<span>Guest</span>
)}
</div>
);
}
// 3. && short-circuit (hiển thị hoặc không)
function Notification({ hasMessage, message }) {
return (
<div>
<h1>Dashboard</h1>
{hasMessage && <div className="alert">{message}</div>}
</div>
);
}
// 4. Nullish coalescing ??
function UserAvatar({ avatarUrl }) {
return (
<img src={avatarUrl ?? '/default-avatar.png'} alt="avatar" />
);
}
// 5. Switch statement qua object map
const statusComponents = {
loading: <Spinner />,
error: <ErrorMessage />,
success: <DataTable />,
};
function DataView({ status }) {
return statusComponents[status] ?? <div>Unknown status</div>;
}
List Rendering
// Luôn cần key prop khi render list
function ProductList({ products }) {
return (
<ul>
{products.map((product) => (
<li key={product.id}>
<span>{product.name}</span>
<span>${product.price}</span>
</li>
))}
</ul>
);
}
// Key phải là unique và stable
// ✅ Dùng id từ data
<li key={item.id}>
// ❌ Tránh dùng index nếu list có thể thay đổi thứ tự
<li key={index}> // Gây bug khi sort/filter
// Render nested lists
function CategoryList({ categories }) {
return (
<div>
{categories.map((category) => (
<div key={category.id}>
<h3>{category.name}</h3>
<ul>
{category.items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
))}
</div>
);
}
// Kết hợp filter và map
function ActiveUsers({ users }) {
return (
<ul>
{users
.filter((user) => user.isActive)
.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
Fragments
import { Fragment } from 'react';
// Cú pháp đầy đủ
function TableRow({ item }) {
return (
<Fragment>
<td>{item.name}</td>
<td>{item.value}</td>
</Fragment>
);
}
// Short syntax <> - không hỗ trợ key
function List({ items }) {
return (
<>
{items.map((item) => (
// Phải dùng Fragment khi cần key
<Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</Fragment>
))}
</>
);
}
React 18 - Concurrent Rendering
import { startTransition, useDeferredValue } from 'react';
// startTransition - đánh dấu update không khẩn cấp
function SearchInput() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
function handleChange(e) {
// Update UI ngay lập tức (khẩn cấp)
setQuery(e.target.value);
// Update kết quả tìm kiếm (không khẩn cấp)
startTransition(() => {
setResults(filterData(e.target.value));
});
}
return (
<>
<input value={query} onChange={handleChange} />
<SearchResults results={results} />
</>
);
}
// useDeferredValue - defer một value
function ProductSearch({ query }) {
const deferredQuery = useDeferredValue(query);
return <ProductList query={deferredQuery} />;
}