Giới thiệu về MDB UI Kit
MDB UI Kit là một thư viện thành phần giao diện hiện đại dựa trên Bootstrap 5, cung cấp hơn 700 thành phần và mẫu có chất lượng cao. Thư viện được xây dựng thuần JavaScript, tuân theo giấy phép mã nguồn mở MIT và nổi bật nhờ kiến trúc sử dụng các mẫu thiết kế (design patterns) thông minh như mẫu kết hợp (Composite Pattern) và mẫu trang trí (Decorator Pattern). Điều này giúp tăng tính tái sử dụng, khả năng mở rộng và dễ bảo trì trong phát triển ứng dụng web.
Phân tích các mẫu thiết kế chính
Mẫu kết hợp: Xây dựng cấu trúc thành phần phân cấp
MDB UI Kit tận dụng mẫu kết hợp để tổ chức hệ thống thành phần theo dạng cây phân cấp. Lớp cơ sở BaseComponent, định nghĩa tại src/js/free/base-component.js, đóng vai trò là nền tảng chung cho mọi thành phần cụ thể.
class BaseComponent {
constructor(element) {
this._element = element;
Data.setData(this._element, this.constructor.DATA_KEY, this);
}
dispose() {
Data.removeData(this._element, this.constructor.DATA_KEY);
EventHandler.off(this._element, this.constructor.EVENT_KEY);
}
static getInstance(element) {
return Data.getData(getElement(element), this.DATA_KEY);
}
}
Tất cả các thành phần như nút bấm, cửa sổ modal hay trình chiếu hình ảnh đều kế thừa từ lớp này, đảm bảo hành vi nhất quán trong vòng đời và quản lý trạng thái. Ví dụ, lớp Button kế thừa từ BSButton nhưng vẫn giữ lại các phương thức chuẩn hóa từ BaseComponent:
class Button extends BSButton {
constructor(element) {
super(element);
this._setupEvents();
}
}
Ở đây, Button hoạt động như một "lá" trong cấu trúc cây – thành phần cuối cùng không chứa các thành phần con khác.
Mẫu trang trí: Mở rộng chức năng linh hoạt
Mẫu trang trí được áp dụng để bổ sung hành vi mới cho các thành phần mà không làm thay đổi cấu trúc ban đầu. Một ví dụ điển hình là hiệu ứng gợn sóng (ripple effect).
Lớp Ripple trong src/js/free/ripple.js không thay thế nút bấm, mà "trang trí" thêm hiệu ứng nhấp vào bất kỳ phần tử nào:
class Ripple extends BaseComponent {
constructor(element, options) {
super(element);
this._options = this._getConfig(options);
Manipulator.addClass(this._element, 'ripple-surface');
this._attachEventHandlers();
}
_attachEventHandlers() {
EventHandler.on(this._element, 'click', (event) => {
this._renderWaveEffect(event);
});
}
_renderWaveEffect(event) {
const wave = document.createElement('span');
wave.classList.add('ripple-wave');
this._positionWave(wave, event);
this._element.appendChild(wave);
// Tự động loại bỏ sau khi hoàn tất
setTimeout(() => wave.remove(), 600);
}
}
Bằng cách này, hiệu ứng ripple có thể được bật/tắt độc lập, không can thiệp vào logic nội tại của thành phần gốc — một đặc điểm cốt lõi của mẫu trang trí.
Ứng dụng thực tế trong hệ thống thành phần
Kết hợp thành phần: Cửa sổ Modal
Cửa sổ modal là ví dụ tiêu biểu cho việc sử dụng mẫu kết hợp. Nó bao gồm nhiều thành phần con như tiêu đề, vùng nội dung, thanh điều khiển và lớp phủ nền — mỗi phần đều có vai trò riêng nhưng phối hợp nhịp nhàng.
- Header: Chứa tiêu đề và nút đóng
- Body: Hiển thị nội dung chính
- Footer: Đặt các nút hành động
- Backdrop: Quản lý tương tác bên ngoài modal
Modal kiểm soát vòng đời và trạng thái hiển thị của tất cả các thành phần con, tạo nên một đơn vị giao diện hoàn chỉnh.
Trang trí chức năng: Nâng cấp nút bấm
Ngoài hiệu ứng ripple, các decorator khác có thể được áp dụng để mở rộng nút bấm:
- Hover animation: Hiệu ứng di chuột
- Loading state: Biểu diễn trạng thái tải
- Disabled overlay: Giao diện khi bị vô hiệu hóa
- Tooltip integration: Thêm gợi ý văn bản
Tất cả các tính năng này đều được thêm vào thông qua cơ chế khởi tạo tùy chọn hoặc thuộc tính dữ liệu, không yêu cầu sửa đổi mã nguồn gốc.
Lợi ích thiết kế và nguyên tắc phát triển
1. Tái sử dụng mã tối đa
Việc dùng chung lớp cơ sở giúp giảm thiểu sự trùng lặp mã. Các phương thức như getInstance() hay dispose() được tái sử dụng trên toàn bộ thư viện.
2. Khả năng mở rộng cao
Mẫu trang trí cho phép người dùng tự do kết hợp các hiệu ứng và hành vi mà không cần thay đổi mã cốt lõi. Điều này phù hợp với nguyên tắc Open/Closed: mở để mở rộng, đóng để sửa đổi.
3. Dễ bảo trì và kiểm thử
Mỗi lớp có trách nhiệm rõ ràng: BaseComponent quản lý vòng đời, Ripple xử lý hiệu ứng, Modal điều phối bố cục. Sự tách biệt này giúp debug và cập nhật nhanh chóng.
4. Tối ưu hiệu suất
Thư viện sử dụng ủy quyền sự kiện (event delegation) và khởi tạo theo nhu cầu (lazy initialization) để giảm tải bộ nhớ. Các trình xử lý sự kiện được tập trung tại src/js/mdb/dom/event-handler.js, tránh tình trạng gắn quá nhiều listener trực tiếp.
Cấu trúc dự án phản ánh tư duy thiết kế
src/ ├── js/ │ ├── free/ # Thành phần miễn phí │ │ ├── base-component.js # Lớp cơ sở - mẫu kết hợp │ │ ├── button.js # Thành phần cụ thể │ │ ├── ripple.js # Trang trí chức năng │ │ └── modal.js # Kết hợp nhiều thành phần │ ├── mdb/ │ │ ├── dom/ # Công cụ thao tác DOM │ │ ├── util/ # Hàm tiện ích │ │ └── perfect-scrollbar/ # Component độc lập │ └── bootstrap/ # Tương thích với Bootstrap └── scss/ # Định kiểu
Gợi ý phát triển và thực hành tốt
Nguyên tắc thiết kế thành phần
- Trách nhiệm đơn lẻ: Mỗi thành phần chỉ đảm nhận một chức năng chính.
- Phụ thuộc vào trừu tượng: Sử dụng interface hoặc lớp cơ sở thay vì phụ thuộc vào chi tiết cụ thể.
- Hạn chế kế thừa sâu: Ưu tiên composition thay vì inheritance nếu có thể.
Tối ưu hiệu suất
- Dùng event delegation để giảm số lượng listener.
- Áp dụng lazy loading cho các thành phần không hiển thị ngay.
- Tối ưu CSS bằng cách giảm độ phức tạp của selector.
Đảm bảo khả năng tiếp cận (Accessibility)
Tất cả thành phần trong MDB UI Kit hỗ trợ điều hướng bằng bàn phím, nhãn ARIA và tương thích với màn đọc, đảm bảo trải nghiệm đồng đều cho mọi người dùng.
Ví dụ triển khai thực tế
Sau đây là cách kết hợp mẫu kết hợp và trang trí trong HTML:
<!-- Nhóm nút: minh họa mẫu kết hợp -->
<div class="btn-group">
<button class="btn btn-primary" data-mdb-ripple-init>Lưu</button>
<button class="btn btn-danger" data-mdb-ripple-init>Xóa</button>
</div>
<!-- Script: Kích hoạt trang trí ripple -->
<script>
// Tự động khởi tạo ripple từ thuộc tính data
document.querySelectorAll('[data-mdb-ripple-init]').forEach(el => {
new Ripple(el);
});
</script>
Ở đây, nhóm nút (btn-group) là một container kết hợp các nút con, còn data-mdb-ripple-init đóng vai trò như một chỉ thị trang trí để bật hiệu ứng.