一键导入
solid-development
SolidJS patterns, reactivity model, and best practices. Use when writing Solid components, reviewing Solid code, or debugging Solid issues.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
SolidJS patterns, reactivity model, and best practices. Use when writing Solid components, reviewing Solid code, or debugging Solid issues.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
React patterns, anti-patterns, and performance optimization. Use when writing React components, reviewing React code, or debugging React issues.
Perform thorough code reviews on pull requests, diffs, or code changes. Use when asked to review code, check a PR, or provide feedback on changes.
基于 SOC 职业分类
| name | solid-development |
| description | SolidJS patterns, reactivity model, and best practices. Use when writing Solid components, reviewing Solid code, or debugging Solid issues. |
Fine-grained reactivity patterns for SolidJS.
SolidJS is NOT React. The mental model is fundamentally different:
| React | SolidJS |
|---|---|
| Components re-run on state change | Components run once |
| Virtual DOM diffing | Direct DOM updates |
| Hooks with dependency arrays | Automatic dependency tracking |
useState returns value | createSignal returns getter function |
Signals are getter/setter pairs that track dependencies automatically:
import { createSignal } from "solid-js";
function Counter() {
const [count, setCount] = createSignal(0);
// ^ getter (function!) ^ setter
return (
<button onClick={() => setCount(c => c + 1)}>
Count: {count()} {/* Call the getter! */}
</button>
);
}
Rules:
count() not countEffects run when their tracked signals change:
import { createSignal, createEffect } from "solid-js";
function Logger() {
const [count, setCount] = createSignal(0);
// ✅ Tracked — runs when count changes
createEffect(() => {
console.log("Count is:", count());
});
// ❌ NOT tracked — runs once at setup
console.log("Initial:", count());
return <button onClick={() => setCount(c => c + 1)}>Increment</button>;
}
Key insight: Only signals accessed inside the effect are tracked.
Cache expensive computations:
import { createSignal, createMemo } from "solid-js";
function FilteredList() {
const [items, setItems] = createSignal([]);
const [filter, setFilter] = createSignal("");
// Only recomputes when items or filter change
const filtered = createMemo(() =>
items().filter(item => item.includes(filter()))
);
return <For each={filtered()}>{item => <div>{item}</div>}</For>;
}
Critical: Destructuring props breaks reactivity.
// ❌ BROKEN — loses reactivity
function Greeting({ name }) {
return <h1>Hello {name}</h1>;
}
// ❌ ALSO BROKEN
function Greeting(props) {
const { name } = props;
return <h1>Hello {name}</h1>;
}
// ✅ CORRECT — maintains reactivity
function Greeting(props) {
return <h1>Hello {props.name}</h1>;
}
For defaults, use mergeProps:
import { mergeProps } from "solid-js";
function Button(props) {
const merged = mergeProps({ variant: "primary" }, props);
return <button class={merged.variant}>{merged.children}</button>;
}
For splitting props, use splitProps:
import { splitProps } from "solid-js";
function Input(props) {
const [local, inputProps] = splitProps(props, ["label"]);
return (
<label>
{local.label}
<input {...inputProps} />
</label>
);
}
Don't use JS control flow in JSX — use Solid's components:
Conditionals with <Show>:
import { Show } from "solid-js";
<Show when={isLoggedIn()} fallback={<Login />}>
<Dashboard />
</Show>
Multiple conditions with <Switch>/<Match>:
import { Switch, Match } from "solid-js";
<Switch>
<Match when={status() === "loading"}>Loading...</Match>
<Match when={status() === "error"}>Error!</Match>
<Match when={status() === "success"}><Data /></Match>
</Switch>
Lists with <For>:
import { For } from "solid-js";
<For each={items()}>
{(item, index) => <li>{index()}: {item.name}</li>}
</For>
<For> vs <Index>:
| Use | When |
|---|---|
<For> | List order/length changes (general case) |
<Index> | Fixed positions, content changes (performance optimization) |
With <Index>, item is a signal: {(item, i) => <div>{item().name}</div>}
Use stores for nested objects and shared state:
import { createStore } from "solid-js/store";
function TodoApp() {
const [state, setState] = createStore({
todos: [],
filter: "all"
});
const addTodo = (text) => {
setState("todos", todos => [...todos, { text, done: false }]);
};
const toggleTodo = (index) => {
setState("todos", index, "done", done => !done);
};
return (/* ... */);
}
When to use:
import { createResource, Suspense } from "solid-js";
function UserProfile(props) {
const [user] = createResource(() => props.userId, fetchUser);
return (
<Suspense fallback={<Loading />}>
<Show when={user()} fallback={<NotFound />}>
<Profile user={user()} />
</Show>
</Suspense>
);
}
Resource properties:
user() — the data (or undefined)user.loading — booleanuser.error — error if faileduser.latest — last successful valueimport { createContext, useContext } from "solid-js";
import { createStore } from "solid-js/store";
const AppContext = createContext();
function AppProvider(props) {
const [state, setState] = createStore({ user: null, theme: "light" });
return (
<AppContext.Provider value={[state, setState]}>
{props.children}
</AppContext.Provider>
);
}
function useApp() {
return useContext(AppContext);
}
| Mistake | Problem | Fix |
|---|---|---|
const { name } = props | Breaks reactivity | Access props.name directly |
count instead of count() | Gets function, not value | Call the signal getter |
console.log(count()) outside effect | Only runs once | Put in createEffect |
Using .map() for lists | No keyed updates | Use <For> component |
| Ternary in JSX for conditionals | Works but less efficient | Use <Show> component |
| Multiple signals for related data | Verbose, hard to manage | Use createStore |
import { createSignal, createMemo, createEffect, Show, For } from "solid-js";
function TaskList(props) {
const [filter, setFilter] = createSignal("all");
// Derived state
const filteredTasks = createMemo(() => {
const f = filter();
if (f === "all") return props.tasks;
return props.tasks.filter(t => (f === "done" ? t.done : !t.done));
});
// Side effect
createEffect(() => {
console.log(`Showing ${filteredTasks().length} tasks`);
});
return (
<div>
<select onChange={e => setFilter(e.target.value)}>
<option value="all">All</option>
<option value="done">Done</option>
<option value="pending">Pending</option>
</select>
<Show when={filteredTasks().length > 0} fallback={<p>No tasks</p>}>
<ul>
<For each={filteredTasks()}>
{task => (
<li classList={{ done: task.done }}>
{task.text}
</li>
)}
</For>
</ul>
</Show>
</div>
);
}