Skip to main content

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 .tsx cho 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 → component ReservationCard.
  • Với component gốc của một thư mục: dùng file index.ts và đặ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).


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 return của file component, sau các nội dung chính, và luôn có comment mô tả rõ block/modal.