SolidJS: Front-end framework mạnh mẽ nhất hiện nay

Với hiệu năng chỉ đứng sau Vanilla JS, hỗ trợ cơ chế reactive với cú pháp đơn giản và triết lý thiết kế gần giống với React, Solid là một front-end framework hoàn hảo nếu bạn đang cần tạo ra một trang web với hiệu năng cao nhưng vẫn dễ tiếp cận và phát triển.

SolidJS poster

Giới thiệu về SolidJS

SolidJS được phát triển vào năm 2018 và nhanh chóng được yêu mến bởi các lập trình viên bởi sự nhỏ gọn, hiện năng cao và dễ tiếp cận nhờ việc sử dụng cơ chế tổ chức code được lấy cảm hứng từ ReactKnockout. Thậm chí, cú pháp code của Solid còn có phần đơn giản hơn React khi không cần đến các ràng buộc khi khởi tạo Hook, đồng thời, Hooks và các ràng buộc (bindings) chỉ được thực thi khi các thành phần phụ thuộc (dependencies) của chúng được cập nhật.

Dưới đây là một đoạn code của Solid, khá dễ hiểu và thân thuộc nếu như bạn đã từng làm qua React phải không nào.

Một đoạn code của SolidJS

Một đoạn code của SolidJS

Ngoài việc tập trung vào hiệu suất và tính đơn giản, Solid vẫn hỗ trợ tích hợp nhiều công cụ cần thiết cho việc phát triển web như TypeScript, các build tool như Vite và Astro, các css framework như Tailwind hay Bootstrap, và một lượng lớn thư viện cần thiết cho chartjs, datetime picker hay i18n (Xem thêm tại ecosystem).

Khái quát về cú pháp của SolidJS

SolidJS có cú pháp gần giống như trong React với việc sử dụng JSX cho render và cơ chế tương tác giữa các biến trạng thái và giao diện cũng gần như nhau nhưng với phần lõi bên trong khác hoàn toàn React.

Sau đây là một số hàm cơ bản tạo nên một Solid mạnh mẽ:

createSignal()

Create signal là một hàm cơ bản nhất của Solid, nó có cú pháp và cơ chế hoạt động giống với hàm “useState()” của React.

Ví dụ về hàm createSignal

Ví dụ về hàm createSignal

Với đoạn code trên, “count signal” sẽ được tạo qua hàm createSignal(), nhờ cơ chế reactive, khi ta gọi hàm setCount(), giá trị trong UI sẽ được thay đổi .Điểm khác biệt duy nhất là “count” sẽ là một hàm “getter” để lấy giá trị.

createEffect()

Tương tự như hàm “useEffect()” trong React, createEffect() cũng là một hàm thực hiện như một người giám sát, và sẽ được thực thi khi giá trị signal thay đổi.

Ví dụ về hàm createEffect

Ví dụ về hàm createEffect

Nhờ vào hệ thống reactive bên dưới, createEffect đủ thông minh để có thể trigger khi “count” signal được thay đổi mà không cần phải truyền vào các tham số cần để theo dõi như trong React Hook.

Ngoài ra, Solid cũng hỗ trợ khá nhiều tính năng hay ho như createMemo, life cycle hooks, control flows,… và còn nhiều thứ khác nữa. Các bạn có thể tìm hiểu rõ hơn tại mục API hay tutorial.

SolidJS vượt trội về performance như thế nào?

Trước khi bắt đầu phân tích tại sao Solid lại nhanh hơn hầu hết các framework front-end hiện nay, ta hãy xem qua bảng so sánh sau:

So sánh hiệu năng của SolidJS với các ngôn ngữ khác

So sánh hiệu năng của SolidJS với các ngôn ngữ khác (Nguồn: https://www.solidjs.com/)

Có thể thấy, Solid chỉ đứng sau mỗi Vanilla, vượt xa hai framework phổ biến hiện nay là React và Vue. Sự vượt trội về hiệu suất đó là nhờ các cơ chế sau đây của SolidJS:

Loại bỏ sự phụ thuộc vào Virtual DOM

Virtual DOM là một kĩ thuật tạo ra một cây DOM ảo – một bản sao của DOM thực sự, nhưng chứa vừa đủ thông tin để có thể truy vấn đến các node. Nhờ việc tạo ra cây DOM ảo này, việc tìm ra các node để update trở nên nhanh chóng hơn. Các bạn có thể tìm hiểu thêm về Virtual DOM tại đây.

Khái quát cách thức hoạt động của virtual dom

Khái quát cách thức hoạt động của Virtual DOM (Nguồn: https://javascript.plainenglish.io/)

Cả hai framework nổi tiếng hiện nay là Vue và React đều sử dụng Virtual DOM cho việc tương tác với giao diện. Tuy nhiên, việc thao tác trên Virtual DOM trông có vẻ “nhanh hơn”, nhưng trên thực chất nó chỉ giúp các framework giảm tải đi bớt độ phức tạp khi thao tác với DOM chứ không thể nào nhanh bằng việc thao tác trực tiếp với cây DOM được.

SolidJS, tuy không cần dùng đến Virtual DOM, vẫn có thể biết chính xác các node nào trên cây DOM cần được update nhờ một cơ chế khá thú vị mà mình sẽ giới thiệu ngay sau đây.

Fine-grained reactivity

Reactivity là cơ chế theo dõi dữ liệu và cập nhật UI khi giá trị đó thay đổi. Cơ chế này được hầu hết các thư viện front-end sử dụng như Vue, React, Angular,…

Với fine-grained reactivity, việc thay đổi UI theo giá trị được theo dõi sẽ được áp dụng ở cấp độ nhỏ nhất, cụ thể là một node trên cây DOM. Để dễ hiểu hơn thì ta xem xét đoạn code sau đây:

function Counter() {
  const [count, setCount] = createSignal(0);
  createEffect(() => {
    console.log("The count is now", count());
  });
  return <div>
    <p>{count}</p>
    <button onClick={() => setCount(count() + 1)}>Click Me</button>
  </div>;
}

render(() => <Counter />, document.getElementById('app'));

Đoạn code trên sẽ hiển thị biến count trên UI và một button để update biến count, bạn có thể thử nó tại đây.

Và đây là output sau khi biên dịch:

import { template as _$template } from "solid-js/web";
import { delegateEvents as _$delegateEvents } from "solid-js/web";
import { createComponent as _$createComponent } from "solid-js/web";
import { insert as _$insert } from "solid-js/web";
const _tmpl$ = /*#__PURE__*/_$template(`<div><p></p><button>Click Me`);
import { render } from 'solid-js/web';
import { createSignal, createEffect } from 'solid-js';
function Counter() {
  const [count, setCount] = createSignal(0);
  createEffect(() => {
    console.log("The count is now", count());
  });
  return (() => {
    const _el$ = _tmpl$(),
      _el$2 = _el$.firstChild,
      _el$3 = _el$2.nextSibling;
    _$insert(_el$2, count);
    _el$3.$$click = () => setCount(count() + 1);
    return _el$;
  })();
}
render(() => _$createComponent(Counter, {}), document.getElementById('app'));
_$delegateEvents(["click"]);

Để ý từ dòng 14 đến 16 thì trong “Counter” component, 3 element là root và 2 element con của DOM sẽ được khởi tạo, sau đó element 2 và 3 sẽ được kết nối với “count” và hàm “setCount()” ở dòng 17 và 18. Điều đó sẽ đảm bảo rằng biến “count” sẽ chỉ được kết nối đến duy nhất element 2, tương tự với hàm “setCount()”.

Các elements trong Solid chỉ thực sự thay đổi theo signal mà nó đang theo dõi. Điều này làm cho việc khởi tạo component chỉ được thực hiện duy nhất một lần, thứ “reactive” theo trạng thái sẽ là element trong component đó, điều này làm cho hiệu suất tăng đáng kể khi không phải cập nhật lại các thành phần không cần thiết. Nếu muốn tìm hiểu sâu hơn về cơ chế reactive trong Solid, bạn có thể đọc thêm tại đây.

Tối ưu hoá hiệu năng với trình biên dịch

Chúng ta đã tìm hiểu về cách các component tương tác với DOM elements, vậy trình biên dịch của Solid đã quản lý và cập nhật các nodes như thế nào mà không phải phụ thuộc vào cây DOM ảo?

Để có thể tương tác với cây DOM, trình biên dịch của Solid sẽ xử lý JSX và quản lý các nodes (bao gồm cả component và JSX expressions) dưới dạng một hàm khởi tạo bên trong một lớp UI ảo. Nhờ cơ chế reactive, khi một trạng thái thay đổi, các hàm phụ thuộc vào nó sẽ được chạy lại, và cây DOM sẽ được cập nhật mà không cần phải thực hiện việc tính toán những thay đổi như khi sử dụng cây DOM ảo. Stelte, cũng có cách thức tương tự để trương tác với cây DOM, điểm khác biệt là các node được biểu diễn dưới dạng class và Stelte dùng ATS (abstract syntax tree) để quản lý các node thay vì JSX.

SolidJS dùng trình biên dịch của mình để tiến hành phân tích, tối ưu hoá và loại bỏ các đoạn code không cần thiết trong quá trình chạy. Điều này làm cho code đã được biên dịch trở nên gọn nhẹ và có hiệu suất cao hơn. Ngoài việc tối ưu hoá khi chuyển đổi code, trình biên dịch của Solid còn có thể thực hiện việc tối ưu hoá cả khi ứng dụng đã đang chạy. Đây là một trong các khác biệt ảnh hưởng đến sự khác biệt về hiệu năng của Solid so với Stelte khi mà trình biên dịch của Stelte chỉ thực hiện việc tối ưu code trong quá trình chuyển đổi. Do phải thực hiện tối ưu code ngay cả khi ứng dụng đang chạy để nâng cao hiệu năng, bundle size của Solid sẽ có phần lớn hơn so với Stelte. Tuy nhiên SolidJS vẫn là một framework nhỏ gọn với bundle size rất nhiều khi so sánh với khác framework khác như Vue hay React.

Tương lai của SolidJS

Hiện tại, Solid đã được hơn 28 nghìn lượt star trong Github, tuy chỉ bằng một phần mười so với Vue hay React, nhưng vẫn là một con số khá ấn tượng cho một ngôn ngữ mới. Mặc dù cộng đồng vẫn còn khá nhỏ, nhưng SolidJS cũng đã có một hệ thống thư viện khá đồ sộ, đủ để giải quyết các vấn đề cần thiết trên FE như build tool, css library, chart, date picker,… Và có lẽ, với sự chạy đua trong việc tối ưu hiệu năng, trong một tương lai không xa, Solid có thể thay thế được và trở thành một ông lớn trong thị trường front-end framework.

Kết lại

Chúng ta vừa trải qua một bài viết khá dài về SolidJS. Hy vọng rằng sau bài viết này, các bạn có thể hiểu rõ hơn về SolidJS và có thể áp dụng vào trong dự án mà mình đang làm. Hẹn gặp lại ở các bài viết sau nhé.

Atekco - Home for Authentic Technical Consultants