mcpField
S
solidjs

solidjs

github.com/suryavirkapur開発1452 回インストール41 週間前に更新
リポジトリ

solidjs について

solidjsは、ユーザーインターフェースを構築する開発者向けの宣言的なJavaScriptライブラリです。テンプレートを実際のDOMにコンパイルし、仮想DOMを使わず細粒度のリアクティビティで更新します。

主な機能

  1. 01細粒度のリアクティブ更新
  2. 02シグナル、エフェクト、メモ
  3. 03非同期リソースと複雑な状態管理
  4. 04条件・リスト・動的コンポーネント
  5. 05コンテキストとライフサイクルAPI
  6. 06Solid RouterとSolidStartのガイド

活用シーン

  1. 01リアクティブなJavaScript UIの構築
  2. 02派生値と非同期データの管理
  3. 03ネストした状態とリスト表示の処理
  4. 04ルーティングやフルスタック設定の確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/suryavirkapur/skills --skill solidjs

インストール後、Claude Code で/solidjsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
api_reference.md
patterns.md
routing.md
solidstart.md

詳細

SolidJS 開発

SolidJS は、細粒度のリアクティビティを備えたユーザーインターフェースを構築するための宣言型 JavaScript ライブラリです。仮想 DOM フレームワークとは異なり、Solid はテンプレートを実際の DOM ノードにコンパイルし、細粒度のリアクションで更新します。

基本原則

  1. コンポーネントは一度だけ実行されます — コンポーネント関数は初期化時のみ実行され、更新のたびには実行されません
  2. 細粒度のリアクティビティ — 変更されたデータに依存する特定の DOM ノードのみが更新されます
  3. 仮想 DOM はありません — コンパイルされたテンプレートを介して DOM を直接操作します
  4. シグナルは関数です — 値には count ではなく count() のように呼び出してアクセスします

リアクティビティのプリミティブ

シグナル — 基本的な状態
import { createSignal } from "solid-js";

const [count, setCount] = createSignal(0);

// Read value (getter)
console.log(count()); // 0

// Update value (setter)
setCount(1);
setCount(prev => prev + 1); // Functional update

オプション:

const [value, setValue] = createSignal(initialValue, {
  equals: false, // Always trigger updates, even if value unchanged
  name: "debugName" // For devtools
});
エフェクト — 副作用
import { createEffect } from "solid-js";

createEffect(() => {
  console.log("Count changed:", count());
  // Runs after render, re-runs when dependencies change
});

主な動作:

  • 初回実行:レンダリング後、ブラウザーの描画前
  • 以降の実行:追跡対象の依存関係が変更されたとき
  • SSR またはハイドレーション中には実行されません
  • クリーンアップ処理には onCleanup を使用します
メモ — 派生値/キャッシュされた値
import { createMemo } from "solid-js";

const doubled = createMemo(() => count() * 2);

// Access like signal
console.log(doubled()); // Cached, only recalculates when count changes

次の場合にメモを使用します:

  • 派生値の計算コストが高い
  • 派生値に複数回アクセスする
  • 結果が変わらない場合に、後続の更新を防ぎたい
リソース — 非同期データ
import { createResource } from "solid-js";

const [user, { mutate, refetch }] = createResource(userId, fetchUser);

// In JSX
<Show when={!user.loading} fallback={<Loading />}>
  <div>{user()?.name}</div>
</Show>

// Resource properties
user.loading   // boolean
user.error     // error if failed
user.state     // "unresolved" | "pending" | "ready" | "refreshing" | "errored"
user.latest    // last successful value

ストア — 複雑な状態

細粒度の更新が必要な、ネストされたオブジェクト/配列の場合:

import { createStore } from "solid-js/store";

const [state, setState] = createStore({
  user: { name: "John", age: 30 },
  todos: []
});

// Path syntax updates
setState("user", "name", "Jane");
setState("todos", todos => [...todos, newTodo]);
setState("todos", 0, "completed", true);

// Produce for immer-like updates
import { produce } from "solid-js/store";
setState(produce(s => {
  s.user.age++;
  s.todos.push(newTodo);
}));

ストアユーティリティ:

  • produce — Immer のような変更処理
  • reconcile — データの差分を検出してパッチを適用します(API レスポンス向け)
  • unwrap — リアクティブではない生のオブジェクトを取得します

コンポーネント

基本的なコンポーネント
import { Component } from "solid-js";

const MyComponent: Component<{ name: string }> = (props) => {
  return <div>Hello, {props.name}</div>;
};
Props の処理
import { splitProps, mergeProps } from "solid-js";

// Default props
const merged = mergeProps({ size: "medium" }, props);

// Split props (for spreading)
const [local, others] = splitProps(props, ["class", "onClick"]);
return <button class={local.class} {...others} />;

Props のルール:

  • Props はリアクティブなゲッターです — トップレベルで分割代入しないでください
  • JSX では const { value } = props ではなく、props.value を使用します
Children ヘルパー
import { children } from "solid-js";

const Wrapper: Component = (props) => {
  const resolved = children(() => props.children);
  
  createEffect(() => {
    console.log("Children:", resolved());
  });
  
  return <div>{resolved()}</div>;
};

制御フローコンポーネント

Show — 条件付きレンダリング
import { Show } from "solid-js";

<Show when={user()} fallback={<Login />}>
  {(user) => <Profile user={user()} />}
</Show>
For — リストのレンダリング(参照をキーとして使用)
import { For } from "solid-js";

<For each={items()} fallback={<Empty />}>
  {(item, index) => (
    <div>{index()}: {item.name}</div>
  )}
</For>

注: index はシグナルで、item は値です。

Index — リストのレンダリング(インデックスをキーとして使用)
import { Index } from "solid-js";

<Index each={items()}>
  {(item, index) => (
    <input value={item().text} />
  )}
</Index>

注: item はシグナルで、index は値です。プリミティブの配列や入力欄に適しています。

Switch/Match — 複数条件
import { Switch, Match } from "solid-js";

<Switch fallback={<Default />}>
  <Match when={state() === "loading"}>
    <Loading />
  </Match>
  <Match when={state() === "error"}>
    <Error />
  </Match>
  <Match when={state() === "success"}>
    <Success />
  </Match>
</Switch>
Dynamic — 動的コンポーネント
import { Dynamic } from "solid-js/web";

<Dynamic component={selected()} someProp="value" />
Portal — DOM 階層の外部へのレンダリング
import { Portal } from "solid-js/web";

<Portal mount={document.body}>
  <Modal />
</Portal>
ErrorBoundary — エラー処理
import { ErrorBoundary } from "solid-js";

<ErrorBoundary fallback={(err, reset) => (
  <div>
    Error: {err.message}
    <button onClick={reset}>Retry</button>
  </div>
)}>
  <RiskyComponent />
</ErrorBoundary>
Suspense — 非同期読み込み
import { Suspense } from "solid-js";

<Suspense fallback={<Loading />}>
  <AsyncComponent />
</Suspense>

Context API

import { createContext, useContext } from "solid-js";

// Create context
const CounterContext = createContext<{
  count: () => number;
  increment: () => void;
}>();

// Provider component
export function CounterProvider(props) {
  const [count, setCount] = createSignal(0);
  
  return (
    <CounterContext.Provider value={{
      count,
      increment: () => setCount(c => c + 1)
    }}>
      {props.children}
    </CounterContext.Provider>
  );
}

// Consumer hook
export function useCounter() {
  const ctx = useContext(CounterContext);
  if (!ctx) throw new Error("useCounter must be used within CounterProvider");
  return ctx;
}

ライフサイクル

import { onMount, onCleanup } from "solid-js";

function MyComponent() {
  onMount(() => {
    console.log("Mounted");
    const handler = () => {};
    window.addEventListener("resize", handler);
    
    onCleanup(() => {
      window.removeEventListener("resize", handler);
    });
  });
  
  return <div>Content</div>;
}

Refs

let inputRef: HTMLInputElement;

<input ref={inputRef} />
<input ref={(el) => { /* el is the DOM element */ }} />

イベント処理

// Standard events (lowercase)
<button onClick={handleClick}>Click</button>
<button onClick={(e) => handleClick(e)}>Click</button>

// Delegated events (on:)
<input on:input={handleInput} />

// Native events (on:) - not delegated
<div on:scroll={handleScroll} />

ルーティング(Solid Router)

ルーティングの完全なガイドは、references/routing.md を参照してください。

クイックセットアップ:

import { Router, Route } from "@solidjs/router";

<Router>
  <Route path="/" component={Home} />
  <Route path="/users/:id" component={User} />
  <Route path="*404" component={NotFound} />
</Router>

SolidStart

フルスタック開発ガイドは、references/solidstart.md を参照してください。

クイックセットアップ:

npm create solid@latest my-app
cd my-app && npm install && npm run dev

よく使われるパターン

条件付きクラス
import { clsx } from "clsx"; // or classList

<div class={clsx("base", { active: isActive() })} />
<div classList={{ active: isActive(), disabled: isDisabled() }} />
更新のバッチ処理
import { batch } from "solid-js";

batch(() => {
  setName("John");
  setAge(30);
  // Effects run once after batch completes
});
Untrack
import { untrack } from "solid-js";

createEffect(() => {
  console.log(count()); // tracked
  console.log(untrack(() => other())); // not tracked
});

TypeScript

import type { Component, ParentComponent, JSX } from "solid-js";

// Basic component
const Button: Component<{ label: string }> = (props) => (
  <button>{props.label}</button>
);

// With children
const Layout: ParentComponent<{ title: string }> = (props) => (
  <div>
    <h1>{props.title}</h1>
    {props.children}
  </div>
);

// Event handler types
const handleClick: JSX.EventHandler<HTMLButtonElement, MouseEvent> = (e) => {
  console.log(e.currentTarget);
};

プロジェクトのセットアップ

# Create new project
npm create solid@latest my-app

# With template
npx degit solidjs/templates/ts my-app

# SolidStart
npm create solid@latest my-app -- --template solidstart

vite.config.ts:

import { defineConfig } from "vite";
import solid from "vite-plugin-solid";

export default defineConfig({
  plugins: [solid()]
});

避けるべきアンチパターン

  1. Props の分割代入 — リアクティビティが失われます

    // ❌ Bad
    const { name } = props;
    
    // ✅ Good
    props.name
    
  2. 追跡スコープ外でのシグナルへのアクセス

    // ❌ Won't update
    console.log(count());
    
    // ✅ Will update
    createEffect(() => console.log(count()));
    
  3. シグナルゲッターの呼び出し忘れ

    // ❌ Passes the function
    <div>{count}</div>
    
    // ✅ Passes the value
    <div>{count()}</div>
    
  4. 配列インデックスをキーとして使用する — 参照をキーとする場合は <For>、インデックスをキーとする場合は <Index> を使用します

  5. レンダリング中の副作用 — createEffect または onMount を使用します

コメント