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