Astro กับ Solid.js: Ultra-fast Reactive UI
#astro13 เม.ย. 2569
Astro กับ Solid.js: Ultra-fast Reactive UI
Solid.js เป็น JavaScript framework ที่ได้รับการยกย่องว่าเร็วที่สุดในบรรดา UI frameworks ทั้งหมด ด้วยแนวคิด fine-grained reactivity ที่ไม่ใช้ Virtual DOM และ compile-time optimizations ทำให้ Solid.js เป็นตัวเลือกที่ยอดเยี่ยมสำหรับ interactive components ใน Astro
ทำไม Solid.js ถึงเร็วมาก?
Solid.js มีสถาปัตยกรรมที่แตกต่างจาก framework อื่น:
- No Virtual DOM: อัปเดต DOM โดยตรงเมื่อ state เปลี่ยน
- Fine-grained Reactivity: อัปเดตเฉพาะส่วนที่เปลี่ยนแปลงจริงๆ
- Compiled Components: Component ถูก compile เป็น efficient JavaScript
- Signals: ระบบ reactivity ที่ทรงพลังและ predictable
การติดตั้ง Solid.js Integration
npx astro add solid
หรือติดตั้งแบบ manual:
npm install @astrojs/solid-js solid-js
แก้ไข astro.config.mjs:
import { defineConfig } from 'astro/config';
import solidJs from '@astrojs/solid-js';
export default defineConfig({
integrations: [solidJs()],
});
Signals: หัวใจของ Solid.js
Signals คือ primitive ของ reactivity ใน Solid.js:
// src/components/SignalDemo.tsx
import { createSignal, createEffect, createMemo } from 'solid-js';
export default function SignalDemo() {
const [count, setCount] = createSignal(0);
const [name, setName] = createSignal('นักพัฒนา');
// Memo: computed value ที่ cache ผลลัพธ์
const doubled = createMemo(() => count() * 2);
// Effect: รันเมื่อ dependency เปลี่ยน
createEffect(() => {
console.log(`Count เปลี่ยนเป็น: ${count()}`);
});
return (
<div>
<p>สวัสดี, {name()}!</p>
<p>Count: {count()}</p>
<p>Doubled: {doubled()}</p>
<button onClick={() => setCount(count() + 1)}>เพิ่ม</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
สร้าง Component ที่ซับซ้อนขึ้น
// src/components/DataTable.tsx
import { createSignal, createResource, For, Show, Suspense } from 'solid-js';
interface User {
id: number;
name: string;
email: string;
role: string;
}
async function fetchUsers(): Promise<User[]> {
const res = await fetch('/api/users');
return res.json();
}
export default function DataTable() {
const [users] = createResource(fetchUsers);
const [search, setSearch] = createSignal('');
const filteredUsers = () =>
users()?.filter(
(u) =>
u.name.toLowerCase().includes(search().toLowerCase()) ||
u.email.toLowerCase().includes(search().toLowerCase())
) ?? [];
return (
<div class="data-table">
<input
type="text"
placeholder="ค้นหาผู้ใช้..."
value={search()}
onInput={(e) => setSearch(e.currentTarget.value)}
/>
<Suspense fallback={<p>กำลังโหลดข้อมูล...</p>}>
<Show
when={filteredUsers().length > 0}
fallback={<p>ไม่พบข้อมูล</p>}
>
<table>
<thead>
<tr>
<th>ชื่อ</th>
<th>อีเมล</th>
<th>บทบาท</th>
</tr>
</thead>
<tbody>
<For each={filteredUsers()}>
{(user) => (
<tr>
<td>{user.name}</td>
<td>{user.email}</td>
<td>{user.role}</td>
</tr>
)}
</For>
</tbody>
</table>
</Show>
</Suspense>
</div>
);
}
Stores ใน Solid.js
สำหรับ state ที่ซับซ้อน ใช้ createStore:
import { createStore, produce } from 'solid-js/store';
interface AppState {
cart: CartItem[];
user: User | null;
theme: 'light' | 'dark';
}
interface CartItem {
id: string;
name: string;
price: number;
qty: number;
}
interface User {
id: string;
name: string;
}
const [state, setState] = createStore<AppState>({
cart: [],
user: null,
theme: 'light',
});
// อัปเดต nested state
function addToCart(item: CartItem) {
setState(
produce((s) => {
const existing = s.cart.find((i) => i.id === item.id);
if (existing) {
existing.qty += 1;
} else {
s.cart.push(item);
}
})
);
}
function toggleTheme() {
setState('theme', (t) => (t === 'light' ? 'dark' : 'light'));
}
Context API ใน Solid.js
// src/context/ThemeContext.tsx
import { createContext, useContext, createSignal } from 'solid-js';
import type { ParentComponent } from 'solid-js';
type Theme = 'light' | 'dark';
interface ThemeContextValue {
theme: () => Theme;
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextValue>();
export const ThemeProvider: ParentComponent = (props) => {
const [theme, setTheme] = createSignal<Theme>('light');
const toggleTheme = () =>
setTheme((t) => (t === 'light' ? 'dark' : 'light'));
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{props.children}
</ThemeContext.Provider>
);
};
export function useTheme() {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error('useTheme must be used within ThemeProvider');
return ctx;
}
ใช้ Solid.js ใน Astro Page
---
import DataTable from '../components/DataTable';
import SignalDemo from '../components/SignalDemo';
---
<html lang="th">
<head>
<title>Astro + Solid.js</title>
</head>
<body>
<h1>Solid.js Components</h1>
<!-- Hydrate ทันที -->
<SignalDemo client:load />
<!-- Hydrate เมื่อ visible -->
<DataTable client:visible />
</body>
</html>
Solid.js vs React: ความแตกต่างหลัก
// React: re-render ทั้ง component
function ReactCounter() {
const [count, setCount] = useState(0);
console.log('Re-render!'); // รันทุกครั้งที่ count เปลี่ยน
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
// Solid.js: อัปเดตเฉพาะ DOM node ที่เปลี่ยน
function SolidCounter() {
const [count, setCount] = createSignal(0);
console.log('Setup!'); // รันครั้งเดียวตอน mount
return <button onClick={() => setCount(c => c + 1)}>{count()}</button>;
}
สรุป
Solid.js เป็นตัวเลือกที่ยอดเยี่ยมสำหรับ interactive components ใน Astro โดยเฉพาะเมื่อต้องการ performance สูงสุด ด้วย fine-grained reactivity และ compiled output ทำให้ Solid.js เหมาะกับ components ที่มีการอัปเดต state บ่อยๆ เช่น real-time dashboards หรือ data tables