ในโลกของ Frontend Development ที่ React ครองตลาดมานาน มี Framework ใหม่ที่น่าสนใจอย่าง SolidJS ที่ท้าทาย React ด้วยแนวคิดที่แตกต่างอย่างสิ้นเชิง SolidJS ใช้ Fine-grained Reactivity โดยไม่ต้องพึ่ง Virtual DOM ทำให้ได้ Performance ที่เร็วกว่า React อย่างเห็นได้ชัดใน Benchmark ทุกตัว
บทความนี้จะสอน SolidJS ตั้งแต่พื้นฐาน แนวคิด Reactive Primitives ไปจนถึง SolidStart (Meta-framework) พร้อมเปรียบเทียบกับ React และ Svelte
SolidJS คืออะไร?
SolidJS เป็น JavaScript UI Library ที่สร้างโดย Ryan Carniato เปิดตัวเวอร์ชัน 1.0 ในปี 2021 SolidJS มีจุดเด่นที่:
- Fine-grained Reactivity: อัปเดตเฉพาะ DOM Node ที่เปลี่ยนจริงๆ ไม่ต้อง Re-render Component ทั้งตัว
- No Virtual DOM: ไม่มี VDOM diffing เขียน JSX แต่ Compile เป็น Real DOM operations ตรงๆ
- Compiled: Compiler จัดการให้ตอน Build ไม่ต้องแบก Runtime ใหญ่
- Tiny Bundle: ขนาดเล็กมาก ~7KB (gzip) เทียบกับ React ~42KB
- JSX Syntax: ใช้ JSX เหมือน React ทำให้ React Developer เรียนรู้ได้เร็ว
ทำไม No Virtual DOM ถึงเร็วกว่า?
// React ทำงานอย่างนี้:
// 1. State เปลี่ยน
// 2. Re-render Component ทั้งตัว → สร้าง VDOM ใหม่
// 3. Diff เปรียบเทียบ VDOM เก่า vs ใหม่
// 4. Patch DOM จริง เฉพาะส่วนที่ต่าง
// → มีค่าใช้จ่าย (Cost) ในขั้นตอน 2, 3
// SolidJS ทำงานอย่างนี้:
// 1. Signal เปลี่ยน
// 2. อัปเดต DOM Node ที่ใช้ Signal นั้นโดยตรง
// → ข้ามขั้นตอน VDOM ทั้งหมด = เร็วกว่า
createSignal — State ของ SolidJS
createSignal คือ Reactive Primitive ตัวหลักของ SolidJS เทียบได้กับ useState ของ React แต่ทำงานต่างกันมาก:
import { createSignal } from "solid-js";
function Counter() {
// createSignal คืน [getter function, setter function]
const [count, setCount] = createSignal(0);
// count() เป็น function ต้องเรียกด้วย ()
// ต่างจาก React ที่ count เป็น value ตรงๆ
return (
<div>
<p>Count: {count()}</p>
<button onClick={() => setCount(count() + 1)}>+1</button>
<button onClick={() => setCount(c => c - 1)}>-1</button>
</div>
);
}
// สิ่งที่ต่างจาก React:
// 1. count เป็น function ต้องเรียก count() ไม่ใช่ count
// 2. Component function ถูกเรียกแค่ครั้งเดียว! (ไม่มี Re-render)
// 3. เมื่อ count เปลี่ยน เฉพาะ <p> ที่แสดง count() จะอัปเดต
createMemo — Computed Value
createMemo คือ Derived state ที่ Cache ผลลัพธ์ไว้ คำนวณใหม่เฉพาะเมื่อ Dependencies เปลี่ยน:
import { createSignal, createMemo } from "solid-js";
function ShoppingCart() {
const [items, setItems] = createSignal([
{ name: "iPhone", price: 35000, qty: 1 },
{ name: "Case", price: 500, qty: 2 },
]);
const [discount, setDiscount] = createSignal(0.1); // 10%
// createMemo — คำนวณใหม่เมื่อ items() หรือ discount() เปลี่ยน
const subtotal = createMemo(() =>
items().reduce((sum, item) => sum + item.price * item.qty, 0)
);
const total = createMemo(() =>
subtotal() * (1 - discount())
);
return (
<div>
<p>Subtotal: {subtotal().toLocaleString()} บาท</p>
<p>Discount: {(discount() * 100)}%</p>
<p>Total: {total().toLocaleString()} บาท</p>
</div>
);
}
createEffect — Side Effects
createEffect ทำงานเมื่อ Dependencies ที่ใช้ภายในเปลี่ยนแปลง (Auto-tracking):
import { createSignal, createEffect } from "solid-js";
function Logger() {
const [name, setName] = createSignal("World");
const [count, setCount] = createSignal(0);
// Effect จะ Track dependencies อัตโนมัติ
// เมื่อ name() เปลี่ยน → Effect จะ Re-run
createEffect(() => {
console.log(`Hello, ${name()}!`);
// ไม่ Track count() เพราะไม่ได้ใช้ใน Effect นี้
});
// Cleanup (เหมือน useEffect return)
createEffect(() => {
const interval = setInterval(() => setCount(c => c + 1), 1000);
// onCleanup ทำงานก่อน Effect Re-run หรือ Component Unmount
onCleanup(() => clearInterval(interval));
});
return (
<div>
<input value={name()} onInput={(e) => setName(e.target.value)} />
<p>Timer: {count()}</p>
</div>
);
}
createResource — Data Fetching
createResource เป็น Built-in primitive สำหรับ Async data fetching:
import { createResource, createSignal, Suspense } from "solid-js";
// Fetcher function
const fetchUser = async (id) => {
const res = await fetch(`https://api.example.com/users/${id}`);
return res.json();
};
function UserProfile() {
const [userId, setUserId] = createSignal(1);
// createResource(source signal, fetcher)
// จะ Re-fetch เมื่อ userId() เปลี่ยน
const [user, { refetch, mutate }] = createResource(userId, fetchUser);
return (
<div>
<input
type="number"
value={userId()}
onInput={(e) => setUserId(Number(e.target.value))}
/>
<Suspense fallback={<p>Loading...</p>}>
<Show when={user()} fallback={<p>No user found</p>}>
<h3>{user().name}</h3>
<p>Email: {user().email}</p>
</Show>
</Suspense>
<button onClick={refetch}>Refresh</button>
</div>
);
}
Show / For / Switch — Control Flow Components
SolidJS ใช้ Control Flow Components แทน JavaScript expressions เพื่อ Optimize DOM updates:
import { Show, For, Switch, Match } from "solid-js";
function App() {
const [loggedIn, setLoggedIn] = createSignal(false);
const [items, setItems] = createSignal(["Apple", "Banana", "Cherry"]);
const [status, setStatus] = createSignal("loading");
return (
<div>
{/* Show — Conditional rendering */}
<Show when={loggedIn()} fallback={<p>Please login</p>}>
<p>Welcome back!</p>
</Show>
{/* For — List rendering (keyed by reference) */}
<ul>
<For each={items()}>
{(item, index) => <li>{index() + 1}. {item}</li>}
</For>
</ul>
{/* Switch/Match — Multi-condition */}
<Switch fallback={<p>Unknown status</p>}>
<Match when={status() === "loading"}>
<p>Loading...</p>
</Match>
<Match when={status() === "error"}>
<p>Error occurred!</p>
</Match>
<Match when={status() === "success"}>
<p>Data loaded!</p>
</Match>
</Switch>
</div>
);
}
// ทำไมใช้ Show/For แทน {condition && ...} หรือ .map()?
// เพราะ Show/For ถูก Optimize สำหรับ Fine-grained reactivity
// - Show: Mount/Unmount DOM จริงๆ ไม่ Re-run Component
// - For: Track แต่ละ Item แยก อัปเดตเฉพาะ Item ที่เปลี่ยน
// - ternary (? :) ก็ใช้ได้ แต่ Show มีประสิทธิภาพกว่า
SolidStart — Meta-framework
SolidStart คือ Meta-framework ของ SolidJS เหมือน Next.js ของ React:
// สร้างโปรเจกต์ SolidStart
npm init solid@latest my-app
// เลือก "with SolidStart"
// โครงสร้างโปรเจกต์
my-app/
├── src/
│ ├── routes/
│ │ ├── index.tsx // → /
│ │ ├── about.tsx // → /about
│ │ ├── users/
│ │ │ ├── index.tsx // → /users
│ │ │ └── [id].tsx // → /users/:id
│ │ └── [...404].tsx // → 404 page
│ ├── components/
│ └── app.tsx
├── app.config.ts
└── package.json
// SolidStart ให้:
// - File-based routing
// - SSR (Server-Side Rendering)
// - API Routes
// - Data fetching (createAsync, cache)
// - Middleware
// - Deployment adapters (Vercel, Netlify, Cloudflare, Node)
SolidStart Data Loading
// routes/users/[id].tsx
import { createAsync, cache } from "@solidjs/router";
// Cache function — ทำงานฝั่ง Server
const getUser = cache(async (id: string) => {
"use server";
const res = await fetch(`https://api.example.com/users/${id}`);
return res.json();
}, "user");
export default function UserPage() {
const params = useParams();
const user = createAsync(() => getUser(params.id));
return (
<Suspense fallback={<p>Loading...</p>}>
<Show when={user()}>
<h1>{user().name}</h1>
<p>{user().email}</p>
</Show>
</Suspense>
);
}
Solid vs React — เปรียบเทียบ
| คุณสมบัติ | SolidJS | React |
|---|---|---|
| Reactivity | Fine-grained (Signal-based) | Coarse-grained (Re-render) |
| Virtual DOM | ไม่มี | มี |
| Component Re-run | ครั้งเดียว (Mount) | ทุกครั้งที่ State เปลี่ยน |
| Bundle Size | ~7 KB (gzip) | ~42 KB (gzip) |
| State | createSignal (getter fn) | useState (value) |
| Derived State | createMemo | useMemo |
| Side Effects | createEffect (auto-track) | useEffect (manual deps) |
| JSX | Compiled to DOM ops | Compiled to createElement |
| Syntax | JSX (คล้าย React มาก) | JSX |
| Meta-framework | SolidStart | Next.js, Remix |
| Ecosystem | เล็กแต่โตเร็ว | ใหญ่มาก |
| Job Market | น้อย | มากที่สุด |
| Performance (JS Framework Benchmark) | อันดับ 1-3 | อันดับ 20+ |
Solid vs Svelte — เปรียบเทียบ
| คุณสมบัติ | SolidJS | Svelte |
|---|---|---|
| Approach | Runtime Reactive (เล็ก) | Compiler-based (No Runtime) |
| Reactivity | Explicit (createSignal) | Implicit ($: reactive statements) |
| Syntax | JSX | .svelte files (HTML-like) |
| Learning Curve จาก React | ง่ายมาก (JSX เหมือนกัน) | ต้องเรียน Syntax ใหม่ |
| TypeScript | First-class support | ดีขึ้นมากใน Svelte 5 |
| Performance | เร็วกว่าเล็กน้อย | เร็วมาก |
| Meta-framework | SolidStart | SvelteKit |
| Community | เล็กกว่า | ใหญ่กว่า |
Solid Store — Nested State Management
import { createStore } from "solid-js/store";
function TodoApp() {
// createStore สำหรับ Nested/Complex state
const [state, setState] = createStore({
user: { name: "John", theme: "dark" },
todos: [
{ id: 1, text: "Learn SolidJS", done: false },
{ id: 2, text: "Build app", done: false },
],
filter: "all",
});
// อัปเดต Nested state ด้วย Path syntax
const toggleTodo = (id) => {
setState("todos", (todo) => todo.id === id, "done", (done) => !done);
};
// เพิ่ม Todo
const addTodo = (text) => {
setState("todos", (todos) => [
...todos,
{ id: Date.now(), text, done: false },
]);
};
// อัปเดต User theme
const toggleTheme = () => {
setState("user", "theme", (t) => (t === "dark" ? "light" : "dark"));
};
return (
<div>
<h2>Todos ({state.user.name})</h2>
<For each={state.todos}>
{(todo) => (
<div>
<input
type="checkbox"
checked={todo.done}
onChange={() => toggleTodo(todo.id)}
/>
<span style={{ "text-decoration": todo.done ? "line-through" : "none" }}>
{todo.text}
</span>
</div>
)}
</For>
</div>
);
}
Routing ใน SolidJS
// ติดตั้ง @solidjs/router
npm install @solidjs/router
import { Router, Route, A } from "@solidjs/router";
// Routes
function App() {
return (
<Router>
<nav>
<A href="/">Home</A>
<A href="/about">About</A>
<A href="/users">Users</A>
</nav>
<Route path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/users/:id" component={UserDetail} />
<Route path="*404" component={NotFound} />
</Router>
);
}
// Dynamic Route
import { useParams } from "@solidjs/router";
function UserDetail() {
const params = useParams();
// params.id จะ Reactive อัตโนมัติ
return <h1>User: {params.id}</h1>;
}
เมื่อไหร่ควรเลือก SolidJS?
| เลือก SolidJS ถ้า | เลือก React ถ้า |
|---|---|
| ต้องการ Performance สูงสุด | ต้องการ Ecosystem ใหญ่ + Library เยอะ |
| Bundle size สำคัญ | ต้องการหางานง่าย |
| รู้จัก React อยู่แล้ว อยากลองของใหม่ | ทีมส่วนใหญ่รู้จัก React |
| โปรเจกต์ใหม่ ไม่ต้องพึ่ง React Ecosystem | ต้องใช้ Library เฉพาะ React (เช่น React Native) |
| สนใจ Fine-grained Reactivity | ต้องการ Community support ใหญ่ |
| ทำ SPA/Dashboard ที่ Performance critical | ทำ Mobile app ด้วย React Native |
สรุป
SolidJS เป็น Framework ที่น่าสนใจมากในปี 2026 ด้วย Fine-grained Reactivity ที่ให้ Performance สูงสุดในบรรดา JavaScript Frameworks ทั้งหมด ใช้ JSX Syntax ที่คุ้นเคยสำหรับ React Developer แต่ทำงานเร็วกว่ามากเพราะไม่มี Virtual DOM
ถ้าคุณเป็น React Developer ที่อยากลองสิ่งใหม่ SolidJS คือทางเลือกที่ดีที่สุด เรียนรู้ได้เร็ว (เพราะ JSX เหมือนกัน) แต่ได้ Performance ที่ดีกว่า เริ่มต้นวันนี้ด้วย npm init solid@latest แล้วคุณจะเข้าใจว่าทำไม Fine-grained Reactivity ถึงเป็นอนาคตของ Frontend Development
