Quy tắc Component
Quy tắc cơ bản cho React component: một component mỗi file, chỉ Function Component, đặt tên file/reference, không mixins, và các quy ước cấu trúc của team (type, UI reuse, input, Modal).
Quy tắc cơ bản
Một component mỗi file
Mỗi file chỉ chứa một React component. Trường hợp ngoại lệ: nhiều Stateless / Pure Component (component không state) có thể đặt chung một file nếu chúng liên quan chặt chẽ.
Luôn dùng cú pháp JSX
Luôn viết component bằng JSX. Không dùng React.createElement trừ khi khởi tạo app từ file không phải JSX/TSX.
// ❌ Tránh
return React.createElement("div", { className: "box" }, "Hello");
// ✅ Tốt
return <div className="box">Hello</div>;
Chỉ sử dụng Function Component
Dự án chỉ dùng Function Component, không dùng Class Component. Component không state nên dùng function thường (không arrow) để tên component hiển thị đúng trong DevTools.
// ✅ Tốt
function CustomerCard({ customer }: { customer: Customer }) {
return <div>{customer.name}</div>;
}
// tránh: arrow function khiến tên component có thể bị hiển thị dạng anonymous
const Listing = ({ hello }: { hello: string }) => <div>{hello}</div>;
// tránh
class CustomerCard extends React.Component { ... }
Không dùng Mixins
Không dùng mixins. Mixin tạo phụ thuộc ngầm, dễ trùng tên và phình logic. Thay vào đó dùng component, HOC (Higher-Order Component) hoặc module tiện ích.
Đặt tên (Naming)
Extension và tên file
- Dùng extension
.tsxcho file chứa React component (dự án TypeScript). - Tên file dùng PascalCase. Ví dụ:
ReservationCard.tsx,UserAvatar.tsx.
Reference: PascalCase cho component, camelCase cho instance
- Component (import / biến component): dùng PascalCase.
- Instance (biến chứa element/component đã render): dùng camelCase.
// ❌ Tránh
import reservationCard from "./ReservationCard";
const ReservationItem = <ReservationCard />;
// ✅ Tốt
import ReservationCard from "./ReservationCard";
const reservationItem = <ReservationCard />;
Tên component trùng tên file; thư mục dùng index
- Tên component (reference) trùng với tên file (bỏ extension). Ví dụ:
ReservationCard.tsx→ componentReservationCard. - Với component gốc của một thư mục: dùng file
index.tsvà đặt tên component theo tên thư mục.
// ❌ Tránh
import Footer from "./Footer/Footer";
import Footer from "./Footer/index";
// ✅ Tốt
import Footer from "./Footer";
Higher-Order Component (HOC): đặt displayName
HOC nên gán displayName dạng withFoo(Bar) (tên HOC + tên component được bọc) để DevTools và thông báo lỗi dễ đọc.
// ❌ Tránh
export default function withFoo(WrappedComponent: React.ComponentType<Props>) {
return function WithFoo(props: Props) {
return <WrappedComponent {...props} foo />;
};
}
// ✅ Tốt
export default function withFoo(WrappedComponent: React.ComponentType<Props>) {
function WithFoo(props: Props) {
return <WrappedComponent {...props} foo />;
}
const name =
WrappedComponent.displayName ?? WrappedComponent.name ?? "Component";
WithFoo.displayName = `withFoo(${name})`;
return WithFoo;
}
Không dùng tên prop trùng DOM
Tránh dùng tên prop của DOM (như style, className) cho mục đích khác. Người đọc mong đợi style/className có nghĩa chuẩn; dùng sai sẽ khó bảo trì và dễ lỗi.
// ❌ Tránh
<MyComponent style="fancy" />
<MyComponent className="fancy" />
// ✅ Tốt
<MyComponent variant="fancy" />
Declaration
Không dùng displayName để đặt tên component
Đặt tên component bằng reference (tên function/const), không dùng displayName. Ví dụ: export function ReservationCard thì component đã có tên rõ ràng.
// ❌ Tránh (pattern cũ)
export default React.createClass({
displayName: "ReservationCard",
// ...
});
// ✅ Tốt
export default function ReservationCard() {
return <div />;
}
Type và cấu trúc
Chỉ định nghĩa type, không dùng interface
Khi định nghĩa props hoặc object shape, dùng type (TypeScript), không dùng interface theo convention dự án.
// ✅ Tốt
type CustomerCardProps = {
customer: Customer;
onSelect?: (id: string) => void;
};
// tránh (theo convention)
interface CustomerCardProps { ... }
UI component tái sử dụng
Dùng component chung (design system / atoms / molecules) cho: số liệu, ngày giờ, avatar, trạng thái (badge, status), email, số điện thoại. Không tự format/style lại từ đầu ở mỗi màn hình.
Ví dụ: Dùng <DateTime value={order.createdAt} />, <PhoneNumber value={customer.phone} /> thay vì format rải rác.
Đúng component cho từng loại input
Dùng đúng component theo design system: TextField (text, email, ...), NumberField (số), Select, DatePicker, Checkbox, v.v. Không dùng <input type="text"> thô khi đã có TextField.
Component không tái sử dụng
Component chỉ sử dụng cho một màn hình cần được đặt trong thư mục components thuộc thư mục màn hình đó. Ví dụ cấu trúc thư mục cho một màn hình có component riêng:
app/
└── [locale]/
└── orgs/
└── [orgId]/
└── vehicles/
├── page.tsx
└── components/
└── VehicleFilter.tsx
VehicleFilter.tsx: Chỉ sử dụng cho màn hình danh sách vehicles này.- Các component chỉ dùng riêng cho màn hình sẽ không đặt chung với components tái sử dụng toàn app.
Việc này giúp code dễ tổ chức, dễ tìm kiếm và tránh lẫn lộn với các component dùng chung.
Sắp xếp component đúng cấp (atomic design): atoms, molecules, organisms
Theo phương pháp atomic design, các component phải được chia thành 3 cấp rõ ràng:
- Atoms — Các thành phần nhỏ nhất, không thể tách nhỏ thêm nữa. Ví dụ: Button, Input, Label, Icon, Checkbox. Atoms thường không chứa logic phức tạp và chỉ xử lý một chức năng nhỏ.
- Molecules — Tổ hợp các atoms kết hợp lại tạo thành một nhóm chức năng có ý nghĩa hơn. Ví dụ: Một form field gồm Label + Input + ErrorMessage, SearchBox (gồm input + icon filter), GroupCheckbox. Molecules giúp tái sử dụng những pattern nhỏ lặp đi lặp lại.
- Organisms — Thành phần lớn gồm nhiều molecules và atoms cấu thành một khối chức năng lớn hoặc một phần của giao diện. Ví dụ: Header của trang, bảng dữ liệu Table, Form section, List with actions, Card lớn.
Hãy luôn xác định rõ component của bạn thuộc lớp nào và đặt vào đúng thư mục (atoms/, molecules/, organisms/), với mục đích:
- Dễ tổ chức, bảo trì.
- Code không lộn xộn giữa các cấp.
- Dễ tái sử dụng ở quy mô phù hợp.
Ví dụ thực tế:
components/
atoms/
Button.tsx
Input.tsx
Label.tsx
molecules/
FormField.tsx // gồm Label + Input + ErrorText
SearchBox.tsx // gồm Input + Button (icon search)
organisms/
Table.tsx
AppHeader.tsx
OrderListSection.tsx
Nếu một component chưa thực sự tái sử dụng hoặc “lớn”, hãy review lại cấp mình chọn cho phù hợp.
Mỗi component có file index.ts
Mỗi component (hoặc thư mục component) có index.ts (barrel) để export; các nơi khác import qua barrel (xem Import Convention).
Modal
Cấu trúc Modal chuẩn
Modal: Modal → ModalHeader, ModalBody, ModalFooter và phải có 1 dòng trống ngăn cách giữa các thành phần trong modal.
<Modal>
<ModalHeader>{/* Tiêu đề */}</ModalHeader>
{/* 1 line trống giữa các phần */}
<ModalBody>{/* Nội dung */}</ModalBody>
{/* 1 line trống giữa các phần */}
<ModalFooter>{/* Hành động */}</ModalFooter>
</Modal>
Khoảng cách và comment trong ModalBody / ModalFooter
Trong ModalBody: cần có khoảng cách rõ ràng giữa các block nội dung bằng margin hoặc spacing, mỗi block nên có comment mô tả chức năng (bằng tiếng Anh).
Trong ModalFooter: các nút (ví dụ Cancel/Submit) phải được tách riêng ra, dễ phân biệt, có comment cho từng nút.
Ví dụ chuẩn:
<Modal>
{/* ...your code here */}
<ModalFooter>
{/* Cancel button - closes modal */}
<Button variant="secondary" onClick={onClose}>
Cancel
</Button>
{/* Confirm button - submits order */}
<Button variant="primary" onClick={onConfirm}>
Confirm
</Button>
</ModalFooter>
</Modal>
Lưu ý: Đặt modal ở cuối phần
returncủa file component, sau các nội dung chính, và luôn có comment mô tả rõ block/modal.