Bỏ qua điều hướng

SolidJS là gì? Framework JavaScript không dùng Virtual DOM

SolidJS là thư viện JavaScript hiệu năng cao không dùng Virtual DOM, dựa trên cơ chế reactivity tinh gọn để tối ưu tốc độ xử lý và bộ nhớ cho ứng dụng web.

Tuan Tran Van
13 phút đọc
Mục lục (9 phần)
  1. SolidJS là gì?
  2. Vì sao SolidJS không cần Virtual DOM?
  3. Signal, effect và memo: ba primitive của reactivity
  4. Component chỉ chạy một lần: mô hình tư duy khác React
  5. Cạm bẫy thường gặp khi mới dùng SolidJS
  6. SolidStart: Solid khi lên full-stack
  7. Solid 1.x và bản 2.0 đang ở đâu?
  8. Khi nào nên chọn SolidJS (và khi nào không)?
  9. Tài liệu tham khảo

SolidJS là một thư viện JavaScript dùng để xây dựng giao diện người dùng dựa trên cơ chế reactivity tinh gọn (fine-grained reactivity).

Khác với React hay Vue, SolidJS hoàn toàn không dùng Virtual DOM. Nó biên dịch mã JSX thẳng sang các thao tác DOM thật, nên bỏ được cả bước so khớp lẫn phần tài nguyên mà bước đó tiêu tốn.

Triết lý vận hành của SolidJS là dựng một đồ thị phụ thuộc (dependency graph) đồng bộ. Trong mô hình này, component chỉ là hàm thiết lập (setup function) và chạy duy nhất một lần lúc khởi tạo. Sau bước thiết lập, SolidJS cập nhật thẳng vào từng node DOM cụ thể khi dữ liệu đổi, thay vì chạy lại toàn bộ logic component rồi đi so khớp cây.

Bỏ Virtual DOM giúp SolidJS chạy gần bằng tốc độ JavaScript thuần. Cách tiếp cận này cũng xoá luôn những khái niệm tốn CPU như mảng phụ thuộc (dependency array) quen thuộc trong React. Bạn mô hình hoá dữ liệu theo đúng cách bạn nghĩ về nó, không phải lo lỗi closure cũ hay những lần re-render thừa làm ứng dụng ì đi.

Bạn đánh đổi một vòng đời render để lấy một đồ thị phụ thuộc.

SolidJS cập nhật thẳng vào DOM thật mà không đi qua Virtual DOM

SolidJS là gì?

SolidJS do Ryan Carniato tạo ra, xây quanh mô hình tín hiệu (signal) để quản lý trạng thái. Đây là một thư viện declarative dùng JSX nhưng biên dịch thành thao tác DOM thật ngay tại thời điểm build. SolidJS đưa ra bộ công cụ tối giản để quản lý reactivity của dữ liệu mà không cần lớp trừu tượng trung gian nào như cây DOM ảo.

Về thông số, runtime của SolidJS chỉ khoảng 7 KB đến 7.6 KB. Theo dữ liệu benchmark, hiệu năng của SolidJS đạt xấp xỉ 1.05x so với vanilla JS (1.00x là tốc độ của mã thuần). Ứng dụng vì thế khởi động nhanh và phản hồi mượt ngay cả trên máy cấu hình thấp hoặc mạng yếu.

Để khởi tạo dự án, bạn dùng lệnh npm init solid. Công cụ này cho chọn các template phổ biến như ts (TypeScript), ts-vitest hoặc ts-tailwindcss. Tạo xong project, bạn chạy npm install rồi npm run dev là bắt đầu phát triển được trên môi trường Vite đã tối ưu sẵn.

Một component Counter đơn giản trong SolidJS trông như sau:

javascript
import { createSignal } from "solid-js";
 
function Counter() {
  const [count, setCount] = createSignal(0);
 
  return <button onClick={() => setCount(count() + 1)}>Count: {count()}</button>;
}

Chi tiết quan trọng nằm ở count(): đó là một hàm getter. Bạn phải gọi nó dưới dạng hàm thì SolidJS mới đăng ký được context và theo dõi thay đổi. Nếu chỉ truyền biến count mà thiếu cặp ngoặc, hệ thống reactivity không ánh xạ được thay đổi nào vào giao diện.

Vì sao SolidJS không cần Virtual DOM?

Mô hình Virtual DOM của React chạy lại toàn bộ hàm component mỗi khi trạng thái đổi, rồi so cây DOM ảo mới với cây cũ để tìm điểm khác biệt. Quá trình đó tốn CPU và tạo rác bộ nhớ. SolidJS bỏ hẳn bước này bằng cách ánh xạ trực tiếp signal vào từng lệnh cập nhật DOM ngay từ lúc biên dịch.

So sánh luồng cập nhật của Virtual DOM và của SolidJS

Dữ liệu benchmark cho thấy khoảng cách khá rõ. Khi tạo 1.000 dòng dữ liệu, SolidJS tốn 1.04x chi phí overhead so với JS thuần, còn React 19 là 1.38x. Ở thao tác thay thế hàng, SolidJS đạt 1.05x so với 1.42x của React. Riêng thao tác chọn một dòng — tương tác người dùng điển hình nhất — SolidJS chỉ mất 1.02x, trong khi React lên tới 1.44x.

Về bộ nhớ, SolidJS chiếm khoảng 1.26x so với vanilla JS tính theo trung bình nhân, thấp hơn nhiều so với mức 2.68x của React. Chi phí bộ nhớ thấp giữ cho ứng dụng ổn định khi xử lý bảng dữ liệu lớn hoặc chạy lâu trên trình duyệt, không rơi vào cảnh giật lag vì cây DOM ảo phình ra.

Cơ chế này chạy được là nhờ SolidJS đăng ký phụ thuộc tại thời điểm chạy (runtime). Khi một signal đổi giá trị, nó chỉ báo cho đúng những node DOM hoặc effect đang thực sự lắng nghe giá trị đó. Không có bước duyệt cây, nên ứng dụng phản hồi gần như tức thì trên thiết bị cấu hình thấp — chỗ mà chi phí tính toán của Virtual DOM thường trở thành nút thắt cổ chai.

Signal, effect và memo: ba primitive của reactivity

createSignal là primitive cốt lõi của SolidJS. Nó trả về một tuple gồm getter và setter: [count, setCount]. Việc getter là một hàm (count()) chính là mấu chốt của cơ chế theo dõi phụ thuộc tự động. Khi hàm này được gọi, SolidJS biết chính xác phạm vi nào — JSX hay effect — đang truy cập dữ liệu để thiết lập quan hệ cập nhật.

Đồ thị phụ thuộc giữa signal, memo và effect trong SolidJS

createEffect tự chạy lại mỗi khi các signal bên trong nó đổi giá trị. Bạn không phải khai báo mảng phụ thuộc thủ công, vì SolidJS ghi lại mọi signal được gọi trong lúc effect thực thi. Điều đó xoá sạch lỗi stale closure và cắt bớt mã rườm rà. Hệ thống dựng một đồ thị phụ thuộc đồng bộ để giữ dữ liệu luôn nhất quán.

createMemo dùng để lưu các giá trị tính toán. Memo tối ưu bằng cách cache kết quả và chỉ tính lại khi signal đầu vào thực sự đổi. Nếu giá trị sau khi tính vẫn bằng giá trị cũ (so sánh ===), những thành phần đang lắng nghe memo đó không bị kích hoạt cập nhật, nên không có tài nguyên nào bị phí.

Ba thành phần phối hợp với nhau như sau:

javascript
const [firstName, setFirstName] = createSignal("John");
const [lastName, setLastName] = createSignal("Smith");
 
// createMemo tính toán fullName và cache lại
const fullName = createMemo(() => `${firstName()} ${lastName()}`);
 
// createEffect tự động chạy khi firstName hoặc lastName thay đổi
createEffect(() => {
  console.log("Họ tên hiện tại:", fullName());
});

Bên trong, SolidJS giữ cho quá trình này diễn ra đồng bộ, không sinh ra trạng thái tạm thời sai lệch. Mọi thay đổi lan truyền qua đồ thị phụ thuộc theo đúng thứ tự, nên giao diện luôn phản ánh đúng dữ liệu tại mọi thời điểm.

Component chỉ chạy một lần: mô hình tư duy khác React

Trong SolidJS, component thực chất là một hàm factory. Nó chạy đúng một lần khi khởi tạo, và mọi logic nằm thẳng trong thân hàm sẽ không bao giờ thực thi lại. Đây là điểm khác biệt cốt lõi so với React, nơi toàn bộ thân hàm component được gọi lại mỗi lần re-render.

Component React chạy lại mỗi lần re-render, còn component SolidJS chỉ chạy một lần

Hệ quả là bạn không dùng được if/else hay toán tử ba ngôi thuần trong JSX nếu muốn chúng phản ứng với dữ liệu. Thay vào đó là các Control Flow component như <Show>, <For>, <Index><Switch>. Chúng là những wrapper reactive, lo việc mount và unmount từng phần DOM mà không phải chạy lại logic của component cha.

Bạn cần phân biệt kỹ <For> với <Index>. <For> theo dõi phần tử theo định danh; khi mảng đổi, nó chỉ di chuyển các node DOM tương ứng, hợp cho danh sách đối tượng. <Index> theo dõi theo vị trí, thường dùng cho mảng chứa giá trị nguyên thuỷ như chuỗi hay số, nơi giá trị đổi nhưng vị trí giữ nguyên.

Mô hình chạy một lần làm code dễ đọc hơn hẳn. Bạn khai báo biến, khởi tạo timer hay mở kết nối socket ngay trong component mà không sợ chúng bị tạo lại vô tận. Mọi thay đổi giao diện được uỷ cho các ràng buộc reactivity nhỏ lẻ bên trong JSX, không phụ thuộc vào luồng thực thi của cả component.

Cạm bẫy thường gặp khi mới dùng SolidJS

Lỗi phổ biến nhất là phá vỡ reactivity khi destructuring props. Trong SolidJS, props là một đối tượng Proxy. Khi bạn viết const { name } = props, bạn đã đọc giá trị tại đúng thời điểm đó và gán nó vào một biến tĩnh, làm đứt liên kết với hệ thống tracking. Muốn giữ reactivity, luôn truy cập qua props.name hoặc dùng hàm tiện ích.

Truy cập props.name giữ được reactivity, còn destructuring props làm đứt liên kết

Solid cung cấp splitPropsmergeProps cho việc này. splitProps tách props thành từng nhóm nhỏ để truyền xuống component con mà vẫn giữ nguyên lớp Proxy. mergeProps gộp các đối tượng props hoặc đặt giá trị mặc định theo cách reactive, để các thuộc tính vẫn được theo dõi chính xác.

Phạm vi theo dõi cũng hay gây bối rối. Một signal chỉ hoạt động reactive khi được gọi trong scope mà Solid đang quản lý — JSX, createEffect hoặc createMemo. Nếu bạn truy cập signal trong một hàm JavaScript thường không được bọc bởi các primitive này, nó chỉ trả về giá trị tĩnh và không cập nhật gì thêm.

Với thực thi bất đồng bộ, hệ thống tracking của Solid chạy đồng bộ. Gọi signal bên trong một setTimeout mà không có cơ chế bảo vệ thì Solid mất dấu context của subscriber. Trường hợp đó bạn nên dùng hàm on để chỉ định thủ công dependency, hoặc dùng createResource để quản lý luồng dữ liệu bất đồng bộ.

SolidStart: Solid khi lên full-stack

SolidStart là meta-framework dựng trên năm trụ cột: Solid lo render, Vite lo bundle, Nitro lo server, Vinxi điều phối, và Seroval lo serialize. Vinxi là mảnh ghép điều phối mã nguồn giữa các môi trường. Cấu trúc rời như vậy khiến SolidStart linh hoạt, đủ để bạn dựng từ một SPA đơn giản đến hệ thống SSR/SSG phức tạp.

Năm trụ cột của SolidStart: Solid, Vite, Nitro, Vinxi và Seroval

Server Functions (dùng chỉ thị "use server") và single-flight mutations là hai điểm sáng kỹ thuật. Single-flight mutations gộp việc thay đổi dữ liệu và tải lại dữ liệu mới vào cùng một request HTTP, cắt hẳn một vòng mạng. Bạn viết logic server ngay cạnh code client, không phải dựng API thủ công.

SolidStart nối với Headless CMS qua createAsync. Primitive này hỗ trợ nạp trước dữ liệu song song với việc render route, nên nội dung hiện gần như tức thì. Dữ liệu từ CMS được fetch qua Server Function, nhờ vậy API token không bao giờ rời khỏi server.

Một đoạn fetch dữ liệu trong SolidStart trông như sau:

javascript
// src/lib/api.ts
import { query } from "@solidjs/router";
 
export const getArticles = query(async () => {
  "use server";
  const res = await fetch(`${process.env.CMS_URL}/api/articles`, {
    headers: { Authorization: `Bearer ${process.env.CMS_TOKEN}` },
  });
  const json = await res.json();
  return json.data;
}, "articles");

Pattern này giữ client bundle nhẹ, trong khi vẫn dùng được sức xử lý của server và khả năng cập nhật giao diện tinh gọn của Solid.

Solid 1.x và bản 2.0 đang ở đâu?

Nhánh 1.x (hiện ở khoảng 1.9.x) là bản ổn định, đã qua kiểm chứng ở nhiều dự án thật. Đây cũng là phiên bản có hệ sinh thái thư viện bổ trợ tốt nhất lúc này. Nếu bạn đang làm ứng dụng cho khách hàng hoặc cần an toàn tuyệt đối về kỹ thuật, Solid 1.x vẫn là lựa chọn hàng đầu.

Hai nhánh Solid 1.x ổn định và Solid 2.0 Release Candidate

Solid 2.0 đang ở giai đoạn Release Candidate. Đây không chỉ là bản cập nhật thư viện lõi mà là một đợt phối hợp đồng bộ của cả hệ sinh thái: Core, Router và SolidStart. Mục tiêu của 2.0 là chuẩn hoá việc xử lý dữ liệu bất đồng bộ và tối ưu hydration trên server, để phát triển full-stack nhất quán hơn.

Hệ sinh thái đang chuẩn hoá quanh createAsync cho việc nạp dữ liệu theo route. Primitive này là một lớp bọc quanh createResource, xử lý single-flight mutations và nạp dữ liệu song song tự nhiên hơn. Nó dần trở thành tiêu chuẩn cho các ứng dụng có routing, giúp quản lý trạng thái tải dữ liệu giữa server và client mà không cần nhiều mã boilerplate.

Nếu bạn mở dự án mới và muốn đón đầu, bản 2.0 RC đáng để cân nhắc. Nhưng đang ở giai đoạn RC nghĩa là một số API nhỏ còn có thể đổi, và bạn phải kiểm tra xem các thư viện bên thứ ba đã hỗ trợ phiên bản này chưa. Đổi lại, 2.0 hứa hẹn trải nghiệm mượt hơn hẳn ở mảng xử lý bất đồng bộ.

Khi nào nên chọn SolidJS (và khi nào không)?

Hãy chọn SolidJS khi bạn dựng dashboard dữ liệu lớn, công cụ biên tập kiểu canvas hay timeline, hoặc real-time feed cần cập nhật liên tục. Hiệu năng cao và mức tiêu thụ bộ nhớ thấp — 1.26x so với 2.68x của React — giữ cho giao diện mượt ngay trên máy yếu. Nếu bạn quan tâm tới kích thước bundle và tốc độ tải trang của dự án, runtime ~7 KB là một lợi thế thật.

Khi nào nên chọn SolidJS và khi nào nên cân nhắc phương án khác

Ngược lại, hãy cân nhắc kỹ nếu dự án phụ thuộc nặng vào hệ sinh thái UI của bên thứ ba — các bộ Data Grid doanh nghiệp hay Date Picker đa năng mà SolidJS chưa có sẵn. Cộng đồng đang lớn nhanh, nhưng khoảng cách số lượng thư viện so với React là chuyện có thật. Và nếu đội của bạn đã quen tư duy re-render của React, việc chuyển sang mô hình reactivity tinh gọn sẽ làm chậm giai đoạn đầu.

SolidJS không phải bản sao chạy nhanh hơn của React. Nó là cách nhìn trực diện hơn về việc trình duyệt thực sự dựng giao diện ra sao. Làm chủ được vài primitive rồi, bạn sẽ thấy trạng thái dễ đoán hơn và ít lỗi vặt hơn hẳn.

Tài liệu tham khảo

Chia sẻ bài viết

X / TwitterFacebookLinkedIn