Mục lục

Kiến trúc nâng cao: Module & Compound Patterns

Thiết kế các thư viện UI và hệ thống mã nguồn có tính đóng gói cao, dễ mở rộng và tái sử dụng.

Khi ứng dụng của bạn không còn là một vài trang web đơn giản mà trở thành một System, việc quản lý sự phụ thuộc và tính đóng gói (encapsulation) trở thành ưu tiên hàng đầu.

1. Module Pattern (Bản gốc)

Trước khi ES6 Modules (import/export) ra đời, JavaScript sử dụng IIFE (Immediately Invoked Function Expression) để tạo ra các module riêng tư. Hiểu điều này giúp bạn làm việc được với các mã nguồn kế thừa (legacy) và hiểu cách các bundler (Webpack) hoạt động.

javascript:
const UIModule = (function() {
  const privateConfig = { theme: 'dark' };
  
  function getTheme() { return privateConfig.theme; }
  
  return {
    init: () => console.log('UI Initialized with', getTheme())
  };
})();

UIModule.init();

2. Vấn đề: Thảm họa "The Giant Component"

Hãy tưởng tượng bạn xây dựng một Modal component. Ban đầu nó rất đơn giản:

javascript:
<Modal title="Hello" content="World" onConfirm={...} />

Nhưng khi yêu cầu tăng lên (thêm icon, thêm loading, thêm custom footer...), bạn bắt đầu nhồi nhét Props:

javascript:
<Modal 
  title="Hello" 
  titleIcon={<Icon />} 
  showCancel={true} 
  isLoading={true} 
  footerContent={<CustomFooter />} 
  // ... và 20 props khác
/>

Thất bại: Component trở nên cực kỳ khó bảo trì, API bị "cứng hóa" và người dùng không thể tùy biến vị trí các thành phần nếu không can thiệp vào mã nguồn gốc.

3. Giải pháp: Compound Components Pattern

Đây là lúc tính năng Compound Pattern ra đời để giải quyết sự bế tắc của Props. Thay vì một "Siêu Component" biết tuốt, chúng ta chia nhỏ thành các thành phần con phối hợp với nhau.

Ví dụ: Sự "giải thoát" của một Tabs component

javascript:
/* 
Thất bại (Cồng kềnh & Khó mở rộng):
<Tabs items={[{label: 'T1', content: 'C1'}]} activeTab={0} />

Giải pháp (Linh hoạt & Đóng gói tốt):
<Tabs>
  <Tabs.List>
    <Tabs.Trigger value="1">Tab 1</Tabs.Trigger>
    <Tabs.Trigger value="2">Tab 2</Tabs.Trigger>
  </Tabs.List>
  <Tabs.Content value="1">Nội dung 1</Tabs.Content>
</Tabs>
*/

3. Thử nghiệm: Xây dựng Logic Compound

Hãy chạy mã dưới đây để thấy cách chúng ta quản lý State dùng chung giữa các component con thông qua Closure (Trong thực tế React sẽ dùng Context).

4. Ưu điểm của Compound Pattern

  1. Giảm thiểu "Prop Drilling": Bạn không cần truyền activeTab qua từng lớp component con.
  2. Tính thẩm mỹ (Separation of Concerns): Mỗi component con chỉ lo việc hiển thị hoặc logic của riêng nó.
  3. Mở rộng linh hoạt: Người dùng thư viện có thể chèn thêm bất kỳ HTML nào ở giữa các thành phần của Menu mà không làm hỏng logic.

5. Senior Note: Module vs Package

  • Module: Là một file hoặc block code thực hiện một nhiệm vụ cụ thể.
  • Package: Là tập hợp các module có thể phân phối được (npm).

Kỹ năng thiết kế module tốt là dấu hiệu nhận biết một Software Architect. Hãy luôn đặt câu hỏi: "Nếu tôi thay đổi logic bên trong module này, có bao nhiêu file khác bên ngoài bị ảnh hưởng?". Nếu câu trả lời là "nhiều", module của bạn đang bị High Coupling (phụ thuộc chặt) - một điều tối kỵ trong kiến trúc.

Tình huống thực tế & Phỏng vấn

Scenario: Thiết kế một Flexible Modal Component

Bối cảnh: Bạn đang xây dựng một thư viện UI cho công ty. Team Marketing muốn Modal có nút đóng ở trên cùng, nhưng team Finance lại muốn nút đóng ở dưới cùng (Footer).

Vấn đề: Nếu bạn dùng một Modal component nhận props như closeButtonPosition, component sẽ ngày càng phình to và khó bảo trì khi có thêm yêu cầu mới.

Giải pháp Senior (Compound Pattern): Chia nhỏ thành <Modal.Header>, <Modal.Body>, <Modal.Footer>. Developer sử dụng có thể tự do sắp xếp vị trí:

javascript:
<Modal>
  <Modal.Footer><CloseButton /></Modal.Footer> {/* Team Finance */}
  <Modal.Body>Content</Modal.Body>
</Modal>

Interview Question: Senior Level

Q: Sự khác biệt giữa CouplingCohesion trong thiết kế Module là gì? Tại sao chúng ta luôn hướng tới "Low Coupling, High Cohesion"?

Gợi ý đáp án

Đáp án:

  • Cohesion (Tính gắn kết): Các thành phần bên trong một module có liên quan mật thiết đến nhau không? (High Cohesion = Tốt, Module tập trung làm một việc).
  • Coupling (Tính phụ thuộc): Module này có biết quá nhiều về module kia không? (Low Coupling = Tốt, thay đổi module này không làm hỏng module kia).

Senior Approach: Thiết kế module sao cho nó giống như một "hộp đen", chỉ giao tiếp qua các inputs/outputs rõ ràng.


Tổng kết

Việc áp dụng Module và Compound Patterns giúp bạn tạo ra những hệ thống mã nguồn "miễn nhiễm" với sự thay đổi. Khi thiết kế Design System cho công ty, hãy ưu tiên Compound Components để mang lại trải nghiệm tốt nhất cho các developer khác trong team.

Quảng cáo
mdhorizontal