solidjs
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSolidJS Development
SolidJS 开发
SolidJS is a declarative JavaScript library for building user interfaces with fine-grained reactivity. Unlike virtual DOM frameworks, Solid compiles templates to real DOM nodes and updates them with fine-grained reactions.
SolidJS是一个声明式JavaScript库,用于构建具备细粒度响应式的用户界面。与虚拟DOM框架不同,Solid会将模板编译为真实DOM节点,并通过细粒度响应机制更新它们。
Core Principles
核心原则
- Components run once — Component functions execute only during initialization, not on every update
- Fine-grained reactivity — Only the specific DOM nodes that depend on changed data update
- No virtual DOM — Direct DOM manipulation via compiled templates
- Signals are functions — Access values by calling: not
count()count
- 组件仅运行一次 — 组件函数仅在初始化时执行,不会在每次更新时运行
- 细粒度响应式 — 只有依赖于变更数据的特定DOM节点会被更新
- 无虚拟DOM — 通过编译后的模板直接操作DOM
- Signals是函数 — 通过调用函数获取值:而非
count()count
Reactivity Primitives
响应式原语
Signals — Basic State
Signals — 基础状态
tsx
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 updateOptions:
tsx
const [value, setValue] = createSignal(initialValue, {
equals: false, // Always trigger updates, even if value unchanged
name: "debugName" // For devtools
});tsx
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选项:
tsx
const [value, setValue] = createSignal(initialValue, {
equals: false, // Always trigger updates, even if value unchanged
name: "debugName" // For devtools
});Effects — Side Effects
Effects — 副作用
tsx
import { createEffect } from "solid-js";
createEffect(() => {
console.log("Count changed:", count());
// Runs after render, re-runs when dependencies change
});Key behaviors:
- Initial run: after render, before browser paint
- Subsequent runs: when tracked dependencies change
- Never runs during SSR or hydration
- Use for cleanup logic
onCleanup
tsx
import { createEffect } from "solid-js";
createEffect(() => {
console.log("Count changed:", count());
// Runs after render, re-runs when dependencies change
});关键特性:
- 初始运行:渲染完成后,浏览器绘制前
- 后续运行:当跟踪的依赖项变更时
- 在SSR或水合过程中从不运行
- 使用处理清理逻辑
onCleanup
Memos — Derived/Cached Values
Memos — 派生/缓存值
tsx
import { createMemo } from "solid-js";
const doubled = createMemo(() => count() * 2);
// Access like signal
console.log(doubled()); // Cached, only recalculates when count changesUse memos when:
- Derived value is expensive to compute
- Derived value is accessed multiple times
- You want to prevent downstream updates when result unchanged
tsx
import { createMemo } from "solid-js";
const doubled = createMemo(() => count() * 2);
// Access like signal
console.log(doubled()); // Cached, only recalculates when count changes在以下场景使用memos:
- 派生值计算成本较高时
- 派生值被多次访问时
- 希望在结果未变更时避免下游更新
Resources — Async Data
Resources — 异步数据
tsx
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 valuetsx
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 valueStores — Complex State
Stores — 复杂状态
For nested objects/arrays with fine-grained updates:
tsx
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);
}));Store utilities:
- — Immer-like mutations
produce - — Diff and patch data (for API responses)
reconcile - — Get raw non-reactive object
unwrap
用于需要细粒度更新的嵌套对象/数组:
tsx
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);
}));Store工具:
- — 类似Immer的变更方式
produce - — 对比并修补数据(用于API响应)
reconcile - — 获取原始非响应式对象
unwrap
Components
组件
Basic Component
基础组件
tsx
import { Component } from "solid-js";
const MyComponent: Component<{ name: string }> = (props) => {
return <div>Hello, {props.name}</div>;
};tsx
import { Component } from "solid-js";
const MyComponent: Component<{ name: string }> = (props) => {
return <div>Hello, {props.name}</div>;
};Props Handling
Props处理
tsx
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 rules:
- Props are reactive getters — don't destructure at top level
- Use in JSX, not
props.valueconst { value } = props
tsx
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是响应式getter — 不要在顶层解构
- 在JSX中使用,而非
props.valueconst { value } = props
Children Helper
子元素助手
tsx
import { children } from "solid-js";
const Wrapper: Component = (props) => {
const resolved = children(() => props.children);
createEffect(() => {
console.log("Children:", resolved());
});
return <div>{resolved()}</div>;
};tsx
import { children } from "solid-js";
const Wrapper: Component = (props) => {
const resolved = children(() => props.children);
createEffect(() => {
console.log("Children:", resolved());
});
return <div>{resolved()}</div>;
};Control Flow Components
控制流组件
Show — Conditional Rendering
Show — 条件渲染
tsx
import { Show } from "solid-js";
<Show when={user()} fallback={<Login />}>
{(user) => <Profile user={user()} />}
</Show>tsx
import { Show } from "solid-js";
<Show when={user()} fallback={<Login />}>
{(user) => <Profile user={user()} />}
</Show>For — List Rendering (keyed by reference)
For — 列表渲染(按引用键控)
tsx
import { For } from "solid-js";
<For each={items()} fallback={<Empty />}>
{(item, index) => (
<div>{index()}: {item.name}</div>
)}
</For>Note: is a signal, is the value.
indexitemtsx
import { For } from "solid-js";
<For each={items()} fallback={<Empty />}>
{(item, index) => (
<div>{index()}: {item.name}</div>
)}
</For>注意: 是signal,是值。
indexitemIndex — List Rendering (keyed by index)
Index — 列表渲染(按索引键控)
tsx
import { Index } from "solid-js";
<Index each={items()}>
{(item, index) => (
<input value={item().text} />
)}
</Index>Note: is a signal, is the value. Better for primitive arrays or inputs.
itemindextsx
import { Index } from "solid-js";
<Index each={items()}>
{(item, index) => (
<input value={item().text} />
)}
</Index>注意: 是signal,是值。更适合原始数组或输入组件。
itemindexSwitch/Match — Multiple Conditions
Switch/Match — 多条件判断
tsx
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>tsx
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 — Dynamic Component
Dynamic — 动态组件
tsx
import { Dynamic } from "solid-js/web";
<Dynamic component={selected()} someProp="value" />tsx
import { Dynamic } from "solid-js/web";
<Dynamic component={selected()} someProp="value" />Portal — Render Outside DOM Hierarchy
Portal — 在DOM层级外渲染
tsx
import { Portal } from "solid-js/web";
<Portal mount={document.body}>
<Modal />
</Portal>tsx
import { Portal } from "solid-js/web";
<Portal mount={document.body}>
<Modal />
</Portal>ErrorBoundary — Error Handling
ErrorBoundary — 错误处理
tsx
import { ErrorBoundary } from "solid-js";
<ErrorBoundary fallback={(err, reset) => (
<div>
Error: {err.message}
<button onClick={reset}>Retry</button>
</div>
)}>
<RiskyComponent />
</ErrorBoundary>tsx
import { ErrorBoundary } from "solid-js";
<ErrorBoundary fallback={(err, reset) => (
<div>
Error: {err.message}
<button onClick={reset}>Retry</button>
</div>
)}>
<RiskyComponent />
</ErrorBoundary>Suspense — Async Loading
Suspense — 异步加载
tsx
import { Suspense } from "solid-js";
<Suspense fallback={<Loading />}>
<AsyncComponent />
</Suspense>tsx
import { Suspense } from "solid-js";
<Suspense fallback={<Loading />}>
<AsyncComponent />
</Suspense>Context API
Context API
tsx
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;
}tsx
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;
}Lifecycle
生命周期
tsx
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>;
}tsx
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
Refs
tsx
let inputRef: HTMLInputElement;
<input ref={inputRef} />
<input ref={(el) => { /* el is the DOM element */ }} />tsx
let inputRef: HTMLInputElement;
<input ref={inputRef} />
<input ref={(el) => { /* el is the DOM element */ }} />Event Handling
事件处理
tsx
// 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} />tsx
// 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} />Routing (Solid Router)
路由(Solid Router)
See references/routing.md for complete routing guide.
Quick setup:
tsx
import { Router, Route } from "@solidjs/router";
<Router>
<Route path="/" component={Home} />
<Route path="/users/:id" component={User} />
<Route path="*404" component={NotFound} />
</Router>完整路由指南请参考 references/routing.md。
快速配置:
tsx
import { Router, Route } from "@solidjs/router";
<Router>
<Route path="/" component={Home} />
<Route path="/users/:id" component={User} />
<Route path="*404" component={NotFound} />
</Router>SolidStart
SolidStart
See references/solidstart.md for full-stack development guide.
Quick setup:
bash
npm create solid@latest my-app
cd my-app && npm install && npm run dev全栈开发指南请参考 references/solidstart.md。
快速搭建:
bash
npm create solid@latest my-app
cd my-app && npm install && npm run devCommon Patterns
常见模式
Conditional Classes
条件类名
tsx
import { clsx } from "clsx"; // or classList
<div class={clsx("base", { active: isActive() })} />
<div classList={{ active: isActive(), disabled: isDisabled() }} />tsx
import { clsx } from "clsx"; // or classList
<div class={clsx("base", { active: isActive() })} />
<div classList={{ active: isActive(), disabled: isDisabled() }} />Batch Updates
批量更新
tsx
import { batch } from "solid-js";
batch(() => {
setName("John");
setAge(30);
// Effects run once after batch completes
});tsx
import { batch } from "solid-js";
batch(() => {
setName("John");
setAge(30);
// Effects run once after batch completes
});Untrack
不追踪
tsx
import { untrack } from "solid-js";
createEffect(() => {
console.log(count()); // tracked
console.log(untrack(() => other())); // not tracked
});tsx
import { untrack } from "solid-js";
createEffect(() => {
console.log(count()); // tracked
console.log(untrack(() => other())); // not tracked
});TypeScript
TypeScript
tsx
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);
};tsx
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);
};Project Setup
项目搭建
bash
undefinedbash
undefinedCreate new project
Create new project
npm create solid@latest my-app
npm create solid@latest my-app
With template
With template
npx degit solidjs/templates/ts my-app
npx degit solidjs/templates/ts my-app
SolidStart
SolidStart
npm create solid@latest my-app -- --template solidstart
**vite.config.ts:**
```ts
import { defineConfig } from "vite";
import solid from "vite-plugin-solid";
export default defineConfig({
plugins: [solid()]
});npm create solid@latest my-app -- --template solidstart
**vite.config.ts:**
```ts
import { defineConfig } from "vite";
import solid from "vite-plugin-solid";
export default defineConfig({
plugins: [solid()]
});Anti-Patterns to Avoid
需避免的反模式
-
Destructuring props — Breaks reactivitytsx
// ❌ Bad const { name } = props; // ✅ Good props.name -
Accessing signals outside tracking scopetsx
// ❌ Won't update console.log(count()); // ✅ Will update createEffect(() => console.log(count())); -
Forgetting to call signal getterstsx
// ❌ Passes the function <div>{count}</div> // ✅ Passes the value <div>{count()}</div> -
Using array index as key — Usefor reference-keyed,
<For>for index-keyed<Index> -
Side effects during render — Useor
createEffectonMount
-
解构Props — 破坏响应式tsx
// ❌ 错误 const { name } = props; // ✅ 正确 props.name -
在追踪作用域外访问Signalstsx
// ❌ 不会更新 console.log(count()); // ✅ 会更新 createEffect(() => console.log(count())); -
忘记调用Signal gettertsx
// ❌ 传递的是函数 <div>{count}</div> // ✅ 传递的是值 <div>{count()}</div> -
使用数组索引作为键 — 按引用键控用,按索引键控用
<For><Index> -
渲染期间产生副作用 — 使用或
createEffectonMount