Gần đây tôi rất quan tâm đến việc so sánh các framework với JavaScript thuần túy. Tôi bắt đầu quan tâm sau một số thất vọng khi sử dụng React trong một số dự án tự do của mình và gần đây tôi đã quen thuộc hơn với các tiêu chuẩn web với tư cách là biên tập viên đặc tả.
Tôi muốn xem điểm chung và khác biệt giữa các framework là gì, nền tảng web có gì để cung cấp như một giải pháp thay thế tinh gọn hơn và liệu nó có đủ không. Mục tiêu của tôi không phải là chỉ trích các framework, mà là tìm hiểu chi phí và lợi ích, xác định xem có giải pháp thay thế nào không và xem liệu chúng ta có thể học hỏi từ giải pháp đó hay không, ngay cả khi chúng ta quyết định sử dụng một framework.
Trong phần đầu tiên này, tôi sẽ đi sâu vào một số tính năng kỹ thuật chung giữa các framework và cách một số framework khác nhau triển khai chúng. Tôi cũng sẽ xem xét chi phí sử dụng các khuôn khổ đó.
Vào những ngày đầu của các khuôn khổ khai báo, khoảng năm 2010, các API DOM đơn giản và dài dòng hơn nhiều, và việc viết các ứng dụng web bằng JavaScript bắt buộc đòi hỏi rất nhiều mã mẫu. Đó là lúc khái niệm “model-view-viewmodel” (MVVM) trở nên phổ biến, với các khuôn khổ Knockout và AngularJS mang tính đột phá khi đó, cung cấp một lớp khai báo JavaScript xử lý sự phức tạp đó bên trong thư viện.
MVVM không phải là một thuật ngữ được sử dụng rộng rãi ngày nay và nó có phần giống với thuật ngữ cũ hơn là “data-binding”.
Tất cả các khuôn khổ UI phổ biến đều cung cấp một số dạng liên kết dữ liệu và hướng dẫn của chúng đều bắt đầu bằng một ví dụ về liên kết dữ liệu.
Sau đây là liên kết dữ liệu trong JSX (SolidJS và React):
Liên kết dữ liệu trong Lit:
Liên kết dữ liệu trong Svelte:
Khi chúng ta có một cách để thể hiện ràng buộc dữ liệu một cách khai báo, chúng ta cần một cách hiệu quả để khuôn khổ lan truyền các thay đổi.
Công cụ React so sánh kết quả của việc kết xuất với kết quả trước đó và áp dụng sự khác biệt cho chính DOM. Cách xử lý sự lan truyền thay đổi này được gọi là DOM ảo.
Trong SolidJS, điều này được thực hiện rõ ràng hơn, với store và các phần tử tích hợp sẵn. Ví dụ, phần tử
Trong Svelte, mã "phản ứng" được tạo ra. Svelte biết những sự kiện nào có thể gây ra thay đổi và nó tạo ra mã đơn giản để phân định ranh giới giữa sự kiện và thay đổi DOM.
Trong Lit, phản ứng được thực hiện bằng cách sử dụng các thuộc tính phần tử, về cơ bản dựa vào phản ứng tích hợp sẵn của các phần tử tùy chỉnh HTML.
SolidJS cung cấp một thành phần điều kiện tích hợp sẵn,
Svelte cung cấp chỉ thị
Trong Lit, bạn sẽ sử dụng một phép toán ba ngôi rõ ràng trong hàm
Trong React, việc xử lý danh sách trông như thế này:
React sử dụng thuộc tính
Trong SolidJS, các phần tử tích hợp
Về mặt nội bộ, SolidJS sử dụng kho lưu trữ riêng của mình kết hợp với
Svelte sử dụng chỉ thị
Lit cung cấp một hàm
Lưu ý: Đây là một chủ đề lớn và tôi hy vọng sẽ đề cập đến nó trong một bài viết trong tương lai vì bài viết này sẽ quá dài.
Chúng cũng cung cấp những thứ quan trọng khác, chẳng hạn như một cách để tái sử dụng các thành phần, nhưng đó là chủ đề của một bài viết riêng.
Các khuôn khổ có hữu ích không? Có. Chúng cung cấp cho chúng ta tất cả các tính năng tiện lợi này. Nhưng đó có phải là câu hỏi đúng để hỏi không? Sử dụng một khuôn khổ phải trả giá. Hãy cùng xem những chi phí đó là gì.
Khung càng biểu cảm và kích thước gói càng nhỏ thì gánh nặng về công cụ xây dựng và thời gian biên dịch càng lớn.
Svelte tuyên bố rằng DOM ảo là chi phí chung. Tôi đồng ý, nhưng có lẽ "xây dựng" (như với Svelte và SolidJS) và các công cụ mẫu phía máy khách tùy chỉnh (như với Lit) cũng là chi phí chung, theo một cách khác?
Mã mà chúng ta thấy khi sử dụng hoặc gỡ lỗi ứng dụng web hoàn toàn khác với mã mà chúng ta đã viết. Bây giờ, chúng ta dựa vào các công cụ gỡ lỗi đặc biệt có chất lượng khác nhau để tiến hành kỹ thuật đảo ngược những gì xảy ra trên trang web và kết nối nó với các lỗi trong mã của riêng chúng ta.
Trong React, ngăn xếp cuộc gọi không bao giờ là "của bạn" — React xử lý việc lập lịch cho bạn. Điều này hoạt động tốt khi không có lỗi. Nhưng hãy thử xác định nguyên nhân của việc kết xuất lại vòng lặp vô hạn và bạn sẽ gặp vô vàn rắc rối.
Trong Svelte, kích thước gói của chính thư viện là nhỏ, nhưng bạn sẽ phải vận chuyển và gỡ lỗi toàn bộ mã được tạo ra một cách bí ẩn, đó là triển khai phản ứng của Svelte, được tùy chỉnh theo nhu cầu của ứng dụng.
Với Lit, vấn đề không phải là xây dựng, mà để gỡ lỗi hiệu quả, bạn phải hiểu công cụ mẫu của nó. Đây có thể là lý do lớn nhất khiến tôi hoài nghi về các khuôn khổ.
Khi bạn tìm kiếm các giải pháp khai báo tùy chỉnh, bạn sẽ phải gỡ lỗi bắt buộc đau đớn hơn. Các ví dụ trong tài liệu này sử dụng Typescript để chỉ định API, nhưng bản thân mã không yêu cầu biên dịch.
Một điều gây khó chịu hơn việc tự sửa lỗi là phải tìm cách giải quyết cho các lỗi khung. Và một điều gây khó chịu hơn lỗi khung là các lỗi xảy ra khi bạn nâng cấp khung lên phiên bản mới mà không sửa đổi mã của mình.
Đúng là vấn đề này cũng tồn tại trong trình duyệt, nhưng khi nó xảy ra, nó xảy ra với tất cả mọi người và trong hầu hết các trường hợp, bản sửa lỗi hoặc giải pháp đã được công bố là sắp xảy ra. Ngoài ra, hầu hết các mẫu trong tài liệu này đều dựa trên API nền tảng web trưởng thành; không phải lúc nào cũng cần phải đi theo hướng tiên tiến nhất.
Trong Phần 2, chúng ta sẽ xem cách giải quyết các vấn đề này mà không cần sử dụng bất kỳ khuôn khổ nào và chúng ta có thể học được gì từ nó. Hãy theo dõi!
Xin gửi lời cảm ơn đặc biệt đến những cá nhân sau đây đã đánh giá kỹ thuật: Yehonatan Daniv, Tom Bigelajzen, Benjamin Greenbaum, Nick Ribal và Louis Lazaris.
Tôi muốn xem điểm chung và khác biệt giữa các framework là gì, nền tảng web có gì để cung cấp như một giải pháp thay thế tinh gọn hơn và liệu nó có đủ không. Mục tiêu của tôi không phải là chỉ trích các framework, mà là tìm hiểu chi phí và lợi ích, xác định xem có giải pháp thay thế nào không và xem liệu chúng ta có thể học hỏi từ giải pháp đó hay không, ngay cả khi chúng ta quyết định sử dụng một framework.
Trong phần đầu tiên này, tôi sẽ đi sâu vào một số tính năng kỹ thuật chung giữa các framework và cách một số framework khác nhau triển khai chúng. Tôi cũng sẽ xem xét chi phí sử dụng các khuôn khổ đó.
Các khuôn khổ
Tôi đã chọn bốn khuôn khổ để xem xét: React, là khuôn khổ thống trị hiện nay, và ba đối thủ mới hơn tuyên bố làm mọi thứ khác với React.- React
“React giúp tạo giao diện người dùng tương tác dễ dàng hơn. Chế độ xem khai báo giúp mã của bạn dễ dự đoán hơn và dễ gỡ lỗi hơn.” - SolidJS
“Solid tuân theo cùng một triết lý như React… Tuy nhiên, nó có một triển khai hoàn toàn khác, không sử dụng DOM ảo.” - Svelte
“Svelte là một cách tiếp cận mới triệt để để xây dựng giao diện người dùng… một bước biên dịch diễn ra khi bạn xây dựng ứng dụng của mình. Thay vì sử dụng các kỹ thuật như phân biệt DOM ảo, Svelte viết mã cập nhật DOM một cách phẫu thuật khi trạng thái ứng dụng của bạn thay đổi.” - Lit
“Xây dựng trên các tiêu chuẩn Web Components, Lit chỉ thêm … khả năng phản ứng, các mẫu khai báo và một số tính năng chu đáo.”
- React giúp việc xây dựng UI dễ dàng hơn với các chế độ xem khai báo.
- SolidJS tuân theo triết lý của React nhưng sử dụng một kỹ thuật khác.
- Svelte sử dụng phương pháp tiếp cận thời gian biên dịch cho UI.
- Lit sử dụng các tiêu chuẩn hiện có, với một số tính năng nhẹ được bổ sung.
Những gì các khuôn khổ giải quyết
Bản thân các khuôn khổ đề cập đến các từ khai báo, phản ứng và DOM ảo. Hãy cùng tìm hiểu ý nghĩa của chúng.Lập trình khai báo
Lập trình khai báo là một mô hình trong đó logic được định nghĩa mà không chỉ định luồng điều khiển. Chúng tôi mô tả kết quả cần có, thay vì các bước sẽ đưa chúng tôi đến đó.Vào những ngày đầu của các khuôn khổ khai báo, khoảng năm 2010, các API DOM đơn giản và dài dòng hơn nhiều, và việc viết các ứng dụng web bằng JavaScript bắt buộc đòi hỏi rất nhiều mã mẫu. Đó là lúc khái niệm “model-view-viewmodel” (MVVM) trở nên phổ biến, với các khuôn khổ Knockout và AngularJS mang tính đột phá khi đó, cung cấp một lớp khai báo JavaScript xử lý sự phức tạp đó bên trong thư viện.
MVVM không phải là một thuật ngữ được sử dụng rộng rãi ngày nay và nó có phần giống với thuật ngữ cũ hơn là “data-binding”.
Data Binding
Data Binding là một cách khai báo để thể hiện cách dữ liệu được đồng bộ hóa giữa một mô hình và giao diện người dùng.Tất cả các khuôn khổ UI phổ biến đều cung cấp một số dạng liên kết dữ liệu và hướng dẫn của chúng đều bắt đầu bằng một ví dụ về liên kết dữ liệu.
Sau đây là liên kết dữ liệu trong JSX (SolidJS và React):
Mã:
function HelloWorld() { const name = "Solid hoặc React"; trả về ( Xin chào {name}! )}
Mã:
class HelloWorld extends LitElement { @property() name = 'lit'; render() { trả về html`
Xin chào ${this.name}!
`; }}
Mã:
let name = 'world';[HEADING=1]Xin chào {name}![/HEADING]
Reactivity
Reactivity là một cách khai báo để thể hiện sự lan truyền của thay đổi.Khi chúng ta có một cách để thể hiện ràng buộc dữ liệu một cách khai báo, chúng ta cần một cách hiệu quả để khuôn khổ lan truyền các thay đổi.
Công cụ React so sánh kết quả của việc kết xuất với kết quả trước đó và áp dụng sự khác biệt cho chính DOM. Cách xử lý sự lan truyền thay đổi này được gọi là DOM ảo.
Trong SolidJS, điều này được thực hiện rõ ràng hơn, với store và các phần tử tích hợp sẵn. Ví dụ, phần tử
Show
sẽ theo dõi những thay đổi bên trong, thay vì DOM ảo.Trong Svelte, mã "phản ứng" được tạo ra. Svelte biết những sự kiện nào có thể gây ra thay đổi và nó tạo ra mã đơn giản để phân định ranh giới giữa sự kiện và thay đổi DOM.
Trong Lit, phản ứng được thực hiện bằng cách sử dụng các thuộc tính phần tử, về cơ bản dựa vào phản ứng tích hợp sẵn của các phần tử tùy chỉnh HTML.
Logic
Khi một khuôn khổ cung cấp giao diện khai báo để liên kết dữ liệu, với việc triển khai phản ứng của nó, nó cũng cần cung cấp một số cách để thể hiện một số logic theo truyền thống được viết theo kiểu bắt buộc. Các khối xây dựng cơ bản của logic là “if” và “for”, và tất cả các khuôn khổ chính đều cung cấp một số biểu thức của các khối xây dựng này.Điều kiện
Ngoài việc ràng buộc dữ liệu cơ bản như số và chuỗi, mọi khuôn khổ đều cung cấp một nguyên hàm “điều kiện”. Trong React, nó trông như thế này:
Mã:
const [hasError, setHasError] = useState(false);return hasError ? Message : null;…setHasError(true);
Hiển thị
:
Mã:
Message
#if
:
Mã:
{#if state.error} Message{/if}
render
:
Mã:
render() { return this.error ? html`Message`: null;}
Danh sách
Nguyên tố khung phổ biến khác là xử lý danh sách. Danh sách là một phần quan trọng của UI — danh sách liên hệ, thông báo, v.v. — và để hoạt động hiệu quả, chúng cần phải phản ứng, không cập nhật toàn bộ danh sách khi một mục dữ liệu thay đổi.Trong React, việc xử lý danh sách trông như thế này:
Mã:
contacts.map((contact, index) =>
[*] {contact.name} )
key
đặc biệt để phân biệt giữa các mục danh sách và đảm bảo rằng toàn bộ danh sách không bị thay thế trong mọi lần kết xuất.Trong SolidJS, các phần tử tích hợp
for
và index
được sử dụng:
Mã:
{contact => {contact.name} }
for
và index
để quyết định phần tử nào sẽ cập nhật khi các mục thay đổi. Nó rõ ràng hơn React, cho phép chúng ta tránh được sự phức tạp của DOM ảo.Svelte sử dụng chỉ thị
each
, được biên dịch dựa trên các trình cập nhật của nó:
Mã:
{#each contacts as contact} {contact.name}{/each}
repeat
, hoạt động tương tự như ánh xạ danh sách dựa trên key
của React:
Mã:
repeat(contacts, contact => contact.id, (contact, index) => html`${contact.name}`
Mô hình thành phần
Một điều nằm ngoài phạm vi của bài viết này là mô hình thành phần trong các khuôn khổ khác nhau và cách xử lý mô hình này bằng cách sử dụng các phần tử HTML tùy chỉnh.Lưu ý: Đây là một chủ đề lớn và tôi hy vọng sẽ đề cập đến nó trong một bài viết trong tương lai vì bài viết này sẽ quá dài.
Chi phí
Các khuôn khổ cung cấp ràng buộc dữ liệu khai báo, các nguyên hàm luồng điều khiển (điều kiện và danh sách), và một cơ chế phản ứng để truyền bá các thay đổi.Chúng cũng cung cấp những thứ quan trọng khác, chẳng hạn như một cách để tái sử dụng các thành phần, nhưng đó là chủ đề của một bài viết riêng.
Các khuôn khổ có hữu ích không? Có. Chúng cung cấp cho chúng ta tất cả các tính năng tiện lợi này. Nhưng đó có phải là câu hỏi đúng để hỏi không? Sử dụng một khuôn khổ phải trả giá. Hãy cùng xem những chi phí đó là gì.
Kích thước gói
Khi xem xét kích thước gói, tôi thích xem xét kích thước thu nhỏ không phải Gzip. Đó là kích thước có liên quan nhất đến chi phí CPU để thực thi JavaScript.- ReactDOM khoảng 120 KB.
- SolidJS khoảng 18 KB.
- Lit khoảng 16 KB.
- Svelte khoảng 2 KB, nhưng kích thước của mã được tạo ra thay đổi.
Xây dựng
Bằng cách nào đó, chúng ta đã quen với việc "xây dựng" các ứng dụng web của mình. Không thể bắt đầu một dự án front-end mà không thiết lập Node.js và một bundler như Webpack, xử lý một số thay đổi cấu hình gần đây trong gói khởi động Babel-TypeScript và tất cả những thứ đó.Khung càng biểu cảm và kích thước gói càng nhỏ thì gánh nặng về công cụ xây dựng và thời gian biên dịch càng lớn.
Svelte tuyên bố rằng DOM ảo là chi phí chung. Tôi đồng ý, nhưng có lẽ "xây dựng" (như với Svelte và SolidJS) và các công cụ mẫu phía máy khách tùy chỉnh (như với Lit) cũng là chi phí chung, theo một cách khác?
Gỡ lỗi
Xây dựng và biên dịch đi kèm với một loại chi phí khác.Mã mà chúng ta thấy khi sử dụng hoặc gỡ lỗi ứng dụng web hoàn toàn khác với mã mà chúng ta đã viết. Bây giờ, chúng ta dựa vào các công cụ gỡ lỗi đặc biệt có chất lượng khác nhau để tiến hành kỹ thuật đảo ngược những gì xảy ra trên trang web và kết nối nó với các lỗi trong mã của riêng chúng ta.
Trong React, ngăn xếp cuộc gọi không bao giờ là "của bạn" — React xử lý việc lập lịch cho bạn. Điều này hoạt động tốt khi không có lỗi. Nhưng hãy thử xác định nguyên nhân của việc kết xuất lại vòng lặp vô hạn và bạn sẽ gặp vô vàn rắc rối.
Trong Svelte, kích thước gói của chính thư viện là nhỏ, nhưng bạn sẽ phải vận chuyển và gỡ lỗi toàn bộ mã được tạo ra một cách bí ẩn, đó là triển khai phản ứng của Svelte, được tùy chỉnh theo nhu cầu của ứng dụng.
Với Lit, vấn đề không phải là xây dựng, mà để gỡ lỗi hiệu quả, bạn phải hiểu công cụ mẫu của nó. Đây có thể là lý do lớn nhất khiến tôi hoài nghi về các khuôn khổ.
Khi bạn tìm kiếm các giải pháp khai báo tùy chỉnh, bạn sẽ phải gỡ lỗi bắt buộc đau đớn hơn. Các ví dụ trong tài liệu này sử dụng Typescript để chỉ định API, nhưng bản thân mã không yêu cầu biên dịch.
Nâng cấp
Trong tài liệu này, tôi đã xem xét bốn khung, nhưng có nhiều khung hơn tôi có thể đếm được (AngularJS, Ember.js và Vue.js, để đặt tên cho một ít). Bạn có thể tin tưởng vào khung, các nhà phát triển, sự chia sẻ ý kiến và hệ sinh thái của khung để làm việc cho bạn khi nó phát triển không?Một điều gây khó chịu hơn việc tự sửa lỗi là phải tìm cách giải quyết cho các lỗi khung. Và một điều gây khó chịu hơn lỗi khung là các lỗi xảy ra khi bạn nâng cấp khung lên phiên bản mới mà không sửa đổi mã của mình.
Đúng là vấn đề này cũng tồn tại trong trình duyệt, nhưng khi nó xảy ra, nó xảy ra với tất cả mọi người và trong hầu hết các trường hợp, bản sửa lỗi hoặc giải pháp đã được công bố là sắp xảy ra. Ngoài ra, hầu hết các mẫu trong tài liệu này đều dựa trên API nền tảng web trưởng thành; không phải lúc nào cũng cần phải đi theo hướng tiên tiến nhất.
Tóm tắt
Chúng tôi đã đi sâu hơn một chút vào việc tìm hiểu các vấn đề cốt lõi mà các khuôn khổ cố gắng giải quyết và cách chúng giải quyết chúng, tập trung vào liên kết dữ liệu, phản ứng, điều kiện và danh sách. Chúng tôi cũng đã xem xét chi phí.Trong Phần 2, chúng ta sẽ xem cách giải quyết các vấn đề này mà không cần sử dụng bất kỳ khuôn khổ nào và chúng ta có thể học được gì từ nó. Hãy theo dõi!
Xin gửi lời cảm ơn đặc biệt đến những cá nhân sau đây đã đánh giá kỹ thuật: Yehonatan Daniv, Tom Bigelajzen, Benjamin Greenbaum, Nick Ribal và Louis Lazaris.
Đọc thêm
- Giới thiệu về khả năng kết hợp toàn bộ ngăn xếp
- Svelte 5 và tương lai của các khuôn khổ: Trò chuyện với Rich Harris
- Xây dựng trình đọc RSS tĩnh để chống lại nỗi sợ FOMO bên trong bạn
- Kỷ nguyên của nền tảng nguyên thủy cuối cùng cũng đã đến