Những vấn đề thực sự tiềm ẩn của Web Components so với các framework hiện đại

05 tháng 4, 2026·6 phút đọc

Bài viết phân tích sâu những hạn chế của Web Components về mặt tiêu thụ bộ nhớ và hiệu suất JIT khi so sánh với các framework như $mol. Ngoài ra, tác giả còn thảo luận về sự khác biệt trong kiến trúc phản ứng, khả năng kiểm thử và tính kế thừa, cho thấy rằng Web Components có thể không phải là giải pháp tối ưu cho các ứng dụng đòi hỏi tốc độ cao.

Những vấn đề thực sự tiềm ẩn của Web Components so với các framework hiện đại

Web Components đã tồn tại một thời gian và được nhiều người kỳ vọng sẽ là tiêu chuẩn mới cho việc phát triển web. Tuy nhiên, liệu chúng thực sự tối ưu như chúng ta nghĩ? Trong bài viết này, chúng ta sẽ cùng "mổ xẻ" những vấn đề cốt lõi của Web Components thông qua việc so sánh hiệu năng với các framework như $mol, Lit và Symbiote.

Để hiểu rõ về Web Components, chúng ta cần nhìn vào những framework thực sự dựa vào nó và đặt "cược" vào công nghệ này.

Vấn đề thứ nhất: Bộ nhớ (Memory)

Một trong những vấn đề lớn nhất là sự chênh lệch về bộ nhớ. Một Web Components tiêu thụ khoảng 124 byte, trong khi một đối tượng JavaScript (JS Object) chỉ chiếm 16 byte.

Sự khác nhau một cấp độ — 10 lần — là một con số rất lớn. Nếu không có cơ chế ảo hóa (virtualization), giao diện người dùng rất likely sẽ bị giật lag.

Trong các framework như Lit hay Symbiote, mỗi todo-item tương ứng với một HTMLElement (nằm trong C++ heap). Điều này có nghĩa là khi bạn tạo ra một Custom Element, bạn buộc phải cấp phát một DOM node ngay lập tức. Ngược lại, $mol coi component là một đối tượng JavaScript thuần túy. DOM chỉ được tạo ra khi quá trình render diễn ra. Với $mol, 1000 task trong mô hình dữ liệu không đồng nghĩa với 1000 phần tử DOM, vì nó chỉ render những thành phần thực sự hiển thị.

Đây là lập luận cũng được tác giả của SolidJS đưa ra. Nếu mục tiêu của bạn là xây dựng framework nhanh nhất có thể, bạn muốn giảm thiểu DOM nodes tối đa. Điều này có nghĩa là Web Components nên được loại bỏ khỏi phương án lựa chọn.

Tóm lại: Nếu bạn muốn hiệu suất tối đa, đừng sử dụng Web Components.

Vấn đề thứ hai: Mất tối ưu hóa JIT

Ngoài việc tiêu tốn nhiều bộ nhớ hơn, việc sử dụng Web Components còn làm chúng ta mất đi lợi thế của tối ưu hóa JIT (Just-In-Time) của JavaScript.

Các thao tác trên DOM chậm hơn rất nhiều so với thao tác trên đối tượng JS:

  • obj.title = x: ~1–2 ns (cơ bản).
  • element.textContent = x: ~30–60 ns (chậm hơn 30 lần).
  • element.setAttribute('class', x): ~50–100 ns (chậm hơn 50 lần).
  • element.style.color = x: ~80–150 ns (chậm hơn 80 lần).

Khi thực hiện các tác vụ như "chọn tất cả" (ví dụ trong email), hiệu năng sẽ suy giảm nghiêm trọng theo số lượng task:

  • Với 100 task: Lit chậm hơn $mol 20 lần.
  • Với 10.000 task: Lit chậm hơn 650 lần.
  • Với 100.000 task: Lit mất đến 120ms (gây lag - một khung hình chỉ nên mất tối đa 16ms), trong khi $mol chỉ mất 15µs (chậm hơn 8.000 lần).

Nguyên nhân nằm ở việc Lit cập nhật thông qua thuộc tính DOM (chậm đi qua liên kết C++), trong khi $mol cập nhật thông qua thuộc tính JS và đẩy DOM vào batch cập nhật qua requestAnimationFrame.

Web Components và ngữ nghĩa "Push"

Web Components sử dụng ngữ nghĩa Push cho tính phản ứng (reactivity). Điều này ảnh hưởng trực tiếp đến số lượng dòng code bạn phải viết. Code càng nhiều thì nguy cơ sai sót càng cao ("đoạn code tốt nhất là đoạn code không bao giờ được viết").

Trong các framework WC như Lit hay Symbiote, bạn phải sử dụng EventTarget và thủ công gọi thông báo khi dữ liệu thay đổi (Push). Mọi thay đổi đều phải được thông báo thủ công và tất cả người đăng ký đều nhận được thông báo đó.

So sánh benchmarkSo sánh benchmark

Ngược lại, $mol sử dụng ngữ nghĩa Pull với đồ thị phụ thuộc tự động. Nó tự động theo dõi các phụ thuộc và chỉ tính toán lại khi chúng thay đổi. Không cần EventTarget hay đăng ký thủ công. Đây cũng là cách tiếp cận mà Vue.js đã chọn.

Khả năng kiểm thử (Testability)

Để kiểm thử một Web Component, bạn bắt buộc phải render nó. Đây là một cách làm cực kỳ kém hiệu quả và tốn thời gian. Trong khi đó, $mol cho phép kiểm thử component dưới dạng đối tượng mà không cần DOM.

Ví dụ, với $mol, bạn chỉ cần gọi phương thức và kiểm tra trạng thái. Với Lit, bạn phải tạo element, gắn vào body, đợi cập nhật hoàn tất và truy cập qua Shadow DOM. Thực tế cho thấy các triển khai dựa trên $mol có thể viết hàng trăm dòng test, trong khi các bản dựa trên WC thường có rất ít hoặc không có do sự phức tạp trong việc thiết lập môi trường test.

Tính kế thừa (Inheritance)

Kế thừa trong Web Components cũng gặp nhiều rắc rối. Ví dụ trong Lit, template thường là một khối HTML nguyên khổng. Nếu bạn muốn thay đổi chỉ một phần nhỏ như chân trang (footer) trong lớp con, bạn buộc phải copy toàn bộ template của lớp cha và sửa lại phần mình cần. Điều này dẫn đến việc duplicate code rất nhiều.

Ngược lại, $mol áp dụng các ràng buộc kiến trúc cứng nhắc hơn nhưng hiệu quả hơn. Bạn có thể kế thừa một component (ví dụ: khu vực cuộn $mol_scroll) và chỉ ghi đè (override) nội dung con (sub) mà thôi. Tất cả logic, style và xử lý sự kiện khác được kế thừa tự động. Không cần copy-paste, không cần code mẫu thừa thãi.

Kết luận

Mọi quyết định kỹ thuật đều là sự đánh đổi, nhưng khi mục tiêu của chúng ta đều giống nhau — xây dựng ứng dụng nhanh, đẹp, dễ bảo trì — chúng ta cần nhìn vào ai giải quyết vấn đề đó tốt nhất.

Tác giả khẳng định rằng $mol đang làm tốt hơn so với các lựa chọn còn lại. Những lo ngại về "vendor lock-in" hay sợ phải học cái mới thường chỉ là những lý do ngụy biện. Ngôn ngữ lập trình luôn thay đổi, và việc học hỏi những công cụ tốt hơn là tất yếu.

Web Components có thể hữu ích trong một số trường hợp cụ thể như nhúng component vào dự án cũ, nhưng nếu mục tiêu là hiệu suất tối thượng và kiến trúc code sạch, có lẽ chúng ta cần cân nhắc lại.

"Nếu mục tiêu của bạn là xây dựng framework nhanh nhất, hãy tối thiểu hóa DOM nodes. Điều này đồng nghĩa với việc Web Components không nằm trong bàn cân."

Bài viết được tổng hợp và biên soạn bằng AI từ các nguồn tin tức công nghệ. Nội dung mang tính tham khảo. Xem bài gốc ↗