solidjs

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

SolidJS 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

核心原则

  1. Components run once — Component functions execute only during initialization, not on every update
  2. Fine-grained reactivity — Only the specific DOM nodes that depend on changed data update
  3. No virtual DOM — Direct DOM manipulation via compiled templates
  4. Signals are functions — Access values by calling:
    count()
    not
    count
  1. 组件仅运行一次 — 组件函数仅在初始化时执行,不会在每次更新时运行
  2. 细粒度响应式 — 只有依赖于变更数据的特定DOM节点会被更新
  3. 无虚拟DOM — 通过编译后的模板直接操作DOM
  4. 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 update
Options:
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
    onCleanup
    for cleanup logic
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 changes
Use 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 value
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 value

Stores — 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:
  • produce
    — Immer-like mutations
  • reconcile
    — Diff and patch data (for API responses)
  • unwrap
    — Get raw non-reactive object
用于需要细粒度更新的嵌套对象/数组:
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工具:
  • produce
    — 类似Immer的变更方式
  • reconcile
    — 对比并修补数据(用于API响应)
  • 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
    props.value
    in JSX, not
    const { 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.value
    ,而非
    const { 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:
index
is a signal,
item
is the value.
tsx
import { For } from "solid-js";

<For each={items()} fallback={<Empty />}>
  {(item, index) => (
    <div>{index()}: {item.name}</div>
  )}
</For>
注意:
index
是signal,
item
是值。

Index — List Rendering (keyed by index)

Index — 列表渲染(按索引键控)

tsx
import { Index } from "solid-js";

<Index each={items()}>
  {(item, index) => (
    <input value={item().text} />
  )}
</Index>
Note:
item
is a signal,
index
is the value. Better for primitive arrays or inputs.
tsx
import { Index } from "solid-js";

<Index each={items()}>
  {(item, index) => (
    <input value={item().text} />
  )}
</Index>
注意:
item
是signal,
index
是值。更适合原始数组或输入组件。

Switch/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 dev

Common 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
undefined
bash
undefined

Create 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

需避免的反模式

  1. Destructuring props — Breaks reactivity
    tsx
    // ❌ Bad
    const { name } = props;
    
    // ✅ Good
    props.name
  2. Accessing signals outside tracking scope
    tsx
    // ❌ Won't update
    console.log(count());
    
    // ✅ Will update
    createEffect(() => console.log(count()));
  3. Forgetting to call signal getters
    tsx
    // ❌ Passes the function
    <div>{count}</div>
    
    // ✅ Passes the value
    <div>{count()}</div>
  4. Using array index as key — Use
    <For>
    for reference-keyed,
    <Index>
    for index-keyed
  5. Side effects during render — Use
    createEffect
    or
    onMount
  1. 解构Props — 破坏响应式
    tsx
    // ❌ 错误
    const { name } = props;
    
    // ✅ 正确
    props.name
  2. 在追踪作用域外访问Signals
    tsx
    // ❌ 不会更新
    console.log(count());
    
    // ✅ 会更新
    createEffect(() => console.log(count()));
  3. 忘记调用Signal getter
    tsx
    // ❌ 传递的是函数
    <div>{count}</div>
    
    // ✅ 传递的是值
    <div>{count()}</div>
  4. 使用数组索引作为键 — 按引用键控用
    <For>
    ,按索引键控用
    <Index>
  5. 渲染期间产生副作用 — 使用
    createEffect
    onMount