SolidJS 是一个声明式 JavaScript 库,用于构建用户界面,它编译为真实 DOM 操作,没有虚拟 DOM 开销。其细粒度响应式系统精确追踪 UI 的哪些部分依赖哪些状态,只更新需要变化的精确 DOM 节点。SolidJS 的性能与手工优化的原生 JavaScript 相当,同时提供 React 开发者熟悉的组件模型和 JSX 语法。
SolidJS 是一个没有虚拟 DOM 的响应式 UI 库,使用细粒度信号进行精确 DOM 更新。它将 JSX 编译为直接的 DOM 指令,提供接近原生 JS 的性能。核心原语包括 createSignal、createEffect 和 createMemo。SolidStart 提供全栈元框架,支持文件路由、SSR 和流式渲染。Solid 在 JS 框架基准测试中始终名列前茅。
- SolidJS 使用细粒度响应式信号,只更新受影响的精确 DOM 节点,无需虚拟 DOM 差异比较。
- Solid 中的组件在设置期间只运行一次,不像 React 那样每次状态变化都重新渲染。
- SolidJS 在构建时将 JSX 编译为优化的真实 DOM 操作,性能与手写原生 JavaScript 相当。
- 响应式原语(createSignal、createMemo、createEffect)自然组合,构成所有状态管理的基础。
- SolidStart 是官方元框架,提供文件路由、服务端渲染、流式传输和 API 路由。
- 从 React 迁移到 Solid 相对容易,因为 Solid 使用 JSX 和类似的组件模型,但响应式语义有显著差异。
什么是 SolidJS 和细粒度响应式?
SolidJS 是由 Ryan Carniato 创建的 UI 库,采用了与 React 截然不同的方法。React 使用虚拟 DOM 来差异比较和批量更新,而 Solid 将 JSX 模板编译为高效的真实 DOM 创建和更新代码。没有协调步骤,没有差异算法。
细粒度响应式意味着框架在单个表达式级别追踪依赖,而不是在组件级别。Solid 中的组件函数只执行一次来建立响应式图。之后更新自动通过图传播,无需重新运行组件。
// How Solid compiles JSX - conceptual overview
// Your JSX code:
function Counter() {
const [count, setCount] = createSignal(0);
return <button onClick={() => setCount(c => c + 1)}>
Clicks: {count()}
</button>;
}
// Solid compiles this to (simplified):
function Counter() {
const [count, setCount] = createSignal(0);
const button = document.createElement("button");
button.onclick = () => setCount(c => c + 1);
const text = document.createTextNode("");
// Only this expression re-runs when count changes:
createEffect(() => text.data = "Clicks: " + count());
button.appendChild(text);
return button;
}SolidJS vs React:关键差异
虽然 Solid 和 React 共享 JSX 语法和基于组件的架构,但它们的执行模型有根本性的不同。
| 特性 | SolidJS | React |
|---|---|---|
| 渲染模型 | 细粒度响应式 DOM 更新 | 虚拟 DOM 差异比较和协调 |
| 组件执行 | 设置时只运行一次 | 每次状态变化都重新运行 |
| 状态原语 | 信号(getter/setter 元组) | useState 钩子(值/setter) |
| 记忆化 | 不需要(无陈旧闭包) | 需要 useMemo、useCallback |
| 包体积 | ~7 KB gzipped | ~42 KB gzipped(含 react-dom) |
| JSX 编译 | 编译为 DOM 指令 | 编译为 createElement 调用 |
| 控制流 | 内置组件(Show、For) | JavaScript 表达式(三元运算、map) |
| 元框架 | SolidStart | Next.js、Remix |
信号、备忘录和副作用
Solid 提供三个核心响应式原语。信号保存响应式值,备忘录高效派生计算值,副作用在依赖变化时运行。
createSignal:响应式状态
信号是一个响应式值容器,返回 getter 函数和 setter 函数。在响应式上下文中读取 getter 会自动订阅变化。
import { createSignal } from "solid-js";
function Counter() {
// Returns [getter, setter] - getter is a function!
const [count, setCount] = createSignal(0);
const [name, setName] = createSignal("World");
return (
<div>
{/* count() calls the getter function */}
<p>Count: {count()}</p>
<p>Hello, {name()}!</p>
{/* Setter accepts value or updater function */}
<button onClick={() => setCount(count() + 1)}>+1</button>
<button onClick={() => setCount(c => c + 1)}>+1 (updater)</button>
<input
value={name()}
onInput={(e) => setName(e.target.value)}
/>
</div>
);
}createMemo:派生计算
备忘录是一个缓存的派生值,只在依赖变化时重新计算。与 React useMemo 不同,Solid 备忘录是真正响应式的。
import { createSignal, createMemo } from "solid-js";
function FilteredList() {
const [items, setItems] = createSignal([
{ name: "Apple", category: "fruit" },
{ name: "Carrot", category: "vegetable" },
{ name: "Banana", category: "fruit" },
]);
const [filter, setFilter] = createSignal("fruit");
// Only recalculates when items() or filter() change
const filtered = createMemo(() =>
items().filter(item => item.category === filter())
);
// Derived from memo - cached, no redundant work
const count = createMemo(() => filtered().length);
return (
<div>
<p>Showing {count()} items</p>
<For each={filtered()}>
{(item) => <p>{item.name}</p>}
</For>
</div>
);
}createEffect:副作用
副作用在其追踪的依赖变化时运行函数。它自动检测读取了哪些信号,只在那些信号更新时重新运行。
import { createSignal, createEffect, onCleanup } from "solid-js";
function Timer() {
const [count, setCount] = createSignal(0);
const [running, setRunning] = createSignal(false);
// Effect tracks running() automatically
createEffect(() => {
if (running()) {
const id = setInterval(() => setCount(c => c + 1), 1000);
// Cleanup runs before next effect or on disposal
onCleanup(() => clearInterval(id));
}
});
// Effect for document title - tracks count()
createEffect(() => {
document.title = "Count: " + count();
});
return (
<div>
<p>{count()}</p>
<button onClick={() => setRunning(r => !r)}>
{running() ? "Stop" : "Start"}
</button>
</div>
);
}组件和 JSX(无虚拟 DOM)
Solid 组件是返回 JSX 的普通函数。关键区别是 Solid 组件函数只执行一次,建立响应式绑定并返回 DOM 结构。
因为组件不重新执行,所以没有陈旧闭包、不需要依赖数组、不需要 useCallback 或 useMemo。访问 props 时不要解构。
import { createSignal } from "solid-js";
// Props must NOT be destructured at parameter level
// BAD: function Greeting({ name, greeting }) { ... }
// GOOD: function Greeting(props) { ... }
function Greeting(props: { name: string; greeting?: string }) {
// Access props inside JSX to keep reactivity
return (
<p>{props.greeting || "Hello"}, {props.name}!</p>
);
}
function App() {
const [name, setName] = createSignal("Solid");
// This component function runs ONCE
console.log("App setup - runs only once");
return (
<div>
{/* When name() changes, only the text node updates */}
<Greeting name={name()} greeting="Welcome" />
<input
value={name()}
onInput={(e) => setName(e.target.value)}
/>
</div>
);
}控制流组件
Solid 提供内置控制流组件,而不是依赖 JavaScript 表达式。这些组件为细粒度响应式优化,确保 DOM 节点的高效创建和销毁。
Show:条件渲染
Show 组件在条件为真时渲染子元素,支持 fallback 属性作为 else 分支。
import { Show, createSignal } from "solid-js";
function UserProfile() {
const [user, setUser] = createSignal(null);
const [loading, setLoading] = createSignal(true);
return (
<div>
<Show when={loading()} fallback={
<Show when={user()} fallback={<p>No user found</p>}>
{(u) => (
<div>
<h2>{u().name}</h2>
<p>{u().email}</p>
</div>
)}
</Show>
}>
<p>Loading...</p>
</Show>
</div>
);
}For:列表渲染
For 组件遍历数组并渲染每个项目。它使用引用身份追踪项目,只更新实际变化的项目。
import { For, createSignal } from "solid-js";
function TodoList() {
const [todos, setTodos] = createSignal([
{ id: 1, text: "Learn Solid", done: false },
{ id: 2, text: "Build an app", done: false },
]);
const addTodo = (text: string) => {
setTodos(prev => [...prev, {
id: Date.now(), text, done: false
}]);
};
return (
<ul>
{/* For tracks items by reference */}
<For each={todos()}>
{(todo, index) => (
<li>
{index() + 1}. {todo.text}
{todo.done ? " (done)" : ""}
</li>
)}
</For>
</ul>
);
}Switch/Match:多分支条件
Switch 和 Match 提供模式匹配风格的条件渲染,比嵌套三元运算更清晰。
import { Switch, Match, createSignal } from "solid-js";
function StatusBadge() {
const [status, setStatus] = createSignal("loading");
return (
<Switch fallback={<span>Unknown status</span>}>
<Match when={status() === "loading"}>
<span style={{ color: "orange" }}>Loading...</span>
</Match>
<Match when={status() === "success"}>
<span style={{ color: "green" }}>Success</span>
</Match>
<Match when={status() === "error"}>
<span style={{ color: "red" }}>Error</span>
</Match>
</Switch>
);
}存储与嵌套响应式
对于复杂的嵌套对象和数组状态,Solid 提供存储。存储是深度响应式代理对象,每个属性都被单独追踪。
存储使用基于路径的 setter API,使不可变风格的更新更加人性化。
import { createStore, produce } from "solid-js/store";
function TodoApp() {
const [state, setState] = createStore({
user: { name: "Alice", theme: "dark" },
todos: [
{ id: 1, text: "Learn Solid stores", done: false },
{ id: 2, text: "Build something", done: false },
],
filter: "all" as "all" | "active" | "done",
});
// Path-based updates - only affected subscribers update
const toggleTodo = (id: number) => {
setState("todos", t => t.id === id, "done", d => !d);
};
// Using produce for mutable-style syntax
const addTodo = (text: string) => {
setState(produce(s => {
s.todos.push({ id: Date.now(), text, done: false });
}));
};
// Nested update - only rerenders theme subscribers
const toggleTheme = () => {
setState("user", "theme", t => t === "dark" ? "light" : "dark");
};
return (
<div>
<p>Theme: {state.user.theme}</p>
<For each={state.todos}>
{(todo) => (
<div onClick={() => toggleTodo(todo.id)}>
{todo.text} {todo.done ? "(done)" : ""}
</div>
)}
</For>
</div>
);
}上下文与依赖注入
Solid 提供类似 React 的上下文 API,用于在组件树中传递数据而无需逐层传递 props。如果上下文值是信号或存储,下游消费者仍会自动响应变化。
import { createContext, useContext, createSignal } from "solid-js";
import type { ParentComponent } from "solid-js";
// Define the context with a type
type ThemeContextType = {
theme: () => string;
setTheme: (t: string) => void;
};
const ThemeContext = createContext<ThemeContextType>();
// Provider component
const ThemeProvider: ParentComponent = (props) => {
const [theme, setTheme] = createSignal("light");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{props.children}
</ThemeContext.Provider>
);
};
// Consumer hook
function useTheme() {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error("useTheme: no ThemeProvider");
return ctx;
}
// Usage in a component
function ThemeToggle() {
const { theme, setTheme } = useTheme();
return (
<button onClick={() => setTheme(
theme() === "light" ? "dark" : "light"
)}>
Current: {theme()}
</button>
);
}SolidStart:元框架
SolidStart 是 SolidJS 的官方元框架,类似于 React 的 Next.js 或 Vue 的 Nuxt。它提供文件路由、服务端渲染、流式传输、API 路由和构建优化。
SolidStart 支持多种渲染模式,包括流式 SSR、静态站点生成和客户端渲染。它使用 Vinxi 作为服务层。
文件路由
路由由 src/routes 目录中的文件结构定义,支持动态参数、捕获全部路由、路由分组和嵌套布局。
// SolidStart file-based routing structure
src/routes/
index.tsx // -> /
about.tsx // -> /about
blog/
index.tsx // -> /blog
[slug].tsx // -> /blog/:slug
users/
[id].tsx // -> /users/:id
[...rest].tsx // -> /users/* (catch-all)
(auth)/ // route group (no URL segment)
login.tsx // -> /login
register.tsx // -> /register// src/routes/blog/[slug].tsx
import { useParams } from "@solidjs/router";
import { createAsync } from "@solidjs/router";
const getPost = async (slug: string) => {
"use server";
return db.query("SELECT * FROM posts WHERE slug = ?", [slug]);
};
export default function BlogPost() {
const params = useParams();
const post = createAsync(() => getPost(params.slug));
return (
<Show when={post()}>
{(p) => (
<article>
<h1>{p().title}</h1>
<div innerHTML={p().content} />
</article>
)}
</Show>
);
}服务器函数
SolidStart 提供仅在服务器上运行但可从客户端调用的服务器函数,使用 "use server" 指令定义。
// Server functions with "use server" directive
import { action, redirect } from "@solidjs/router";
// Server function for data fetching
async function getUsers() {
"use server";
return db.query("SELECT id, name, email FROM users");
}
// Server action for mutations
const createUser = action(async (formData: FormData) => {
"use server";
const name = formData.get("name") as string;
const email = formData.get("email") as string;
await db.query(
"INSERT INTO users (name, email) VALUES (?, ?)",
[name, email]
);
throw redirect("/users");
});
// Using in a component
export default function NewUser() {
return (
<form action={createUser} method="post">
<input name="name" placeholder="Name" />
<input name="email" type="email" placeholder="Email" />
<button type="submit">Create User</button>
</form>
);
}Resource API 异步数据
createResource 原语处理异步数据获取,内置加载和错误状态,与 Suspense 集成实现声明式加载 UI。
import { createResource, createSignal, Suspense, ErrorBoundary } from "solid-js";
const fetchUser = async (id: number) => {
const res = await fetch("/api/users/" + id);
if (!res.ok) throw new Error("Failed to fetch user");
return res.json();
};
function UserCard() {
const [userId, setUserId] = createSignal(1);
// Re-fetches when userId() changes
const [user, { refetch, mutate }] = createResource(
userId,
fetchUser
);
return (
<ErrorBoundary fallback={(err) => <p>Error: {err.message}</p>}>
<Suspense fallback={<p>Loading user...</p>}>
<Show when={user()}>
{(u) => (
<div>
<h2>{u().name}</h2>
<p>{u().email}</p>
<button onClick={refetch}>Refresh</button>
</div>
)}
</Show>
</Suspense>
<button onClick={() => setUserId(id => id + 1)}>
Next User
</button>
</ErrorBoundary>
);
}TypeScript 集成
SolidJS 拥有一流的 TypeScript 支持。信号、存储、组件和所有 API 都有完整类型。
组件 props 使用标准 TypeScript 接口或类型别名进行类型定义。泛型组件、联合类型 props 和严格事件类型都能自然工作。
import { Component, JSX } from "solid-js";
// Typed component props
interface ButtonProps {
variant: "primary" | "secondary" | "danger";
size?: "sm" | "md" | "lg";
disabled?: boolean;
onClick?: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>;
children: JSX.Element;
}
const Button: Component<ButtonProps> = (props) => {
return (
<button
disabled={props.disabled}
onClick={props.onClick}
data-variant={props.variant}
data-size={props.size || "md"}
>
{props.children}
</button>
);
};性能基准:为什么 Solid 快
SolidJS 在 JavaScript 框架基准测试中始终名列前茅。在 JS Framework Benchmark 中,Solid 的性能与手工优化的原生 JavaScript 相差不到几个百分点。原因如下:
- 无虚拟 DOM 开销:更新直接作用于真实 DOM,无需差异比较。
- 编译时优化:JSX 编译为高效的 DOM 创建代码。
- 细粒度追踪:只重新计算依赖于变化数据的精确表达式。
- 无组件重新执行:组件函数只运行一次,无需重新渲染整个子树。
- 最小内存分配:更新时不分配中间虚拟 DOM 树结构。
- 小包体积:Solid 核心约 7 KB gzipped,而 React + ReactDOM 约 42 KB。
// Benchmark-style comparison (conceptual)
// Creating 1000 rows and updating every 10th row
// React: re-renders entire list component
// - Calls component function
// - Creates new virtual DOM tree (1000 nodes)
// - Diffs old vs new tree
// - Patches 100 real DOM nodes
// Solid: updates only affected DOM nodes
// - No component re-execution
// - No virtual DOM tree allocation
// - No diffing algorithm
// - Directly updates 100 text nodes via signal subscriptions
// JS Framework Benchmark results (lower is better):
// vanilla JS: 1.00 (baseline)
// Solid: 1.04
// Svelte: 1.19
// Vue: 1.36
// React: 1.55
// Angular: 1.42生态系统和社区
Solid 生态系统已快速成长,包含常用开发需求的关键库:
- solid-router - undefined
- solid-primitives - undefined
- Kobalte - undefined
- solid-query - undefined
- solid-testing-library - undefined
- solid-transition-group - undefined
- solid-markdown - undefined
// Getting started with SolidJS
# Create a new Solid project
npx degit solidjs/templates/ts my-solid-app
cd my-solid-app
npm install
npm run dev
# Create a SolidStart project
npm init solid@latest my-solid-start-app
cd my-solid-start-app
npm install
npm run dev
# Key packages to install
npm install @solidjs/router # routing
npm install solid-primitives # utility primitives
npm install @kobalte/core # UI components从 React 迁移到 Solid
从 React 迁移到 Solid 比转向完全不同的范式更容易,因为两者都使用 JSX 和基于组件的架构。但需要几个关键的思维模型转变:
- 用 createSignal 替换 useState。注意 getter 是函数需要调用:count() 而不是 count。
- 用 createEffect 替换 useEffect。不需要依赖数组,Solid 自动追踪依赖。
- 用 createMemo 替换 useMemo。不需要依赖数组。
- 不要解构 props。在 JSX 或响应式上下文中使用 props.name 以保持响应式。
- 用 Show 组件替换条件渲染三元运算符。用 For 组件替换 array.map。
- 完全移除 useCallback。Solid 中的闭包永远不会过期,因为组件不会重新执行。
- 用简单的 let 变量替换 useRef 作为 DOM 引用。使用 ref 回调来赋值。
// React version // Solid equivalent
// useState("") + dep arrays // createSignal("") - no dep arrays
// useMemo(() => ..., [items, query]) // createMemo(() => ...)
// useCallback((e) => ..., []) // Not needed in Solid
// useEffect(() => ..., [filtered.length]) // createEffect(() => ...)
// value={query} // value={query()}
// onChange={handler} // onInput={e => setQuery(...)}
// filtered.map(i => <p key={i.id}>...) // <For each={filtered()}>...
import { createSignal, createMemo, createEffect, For } from "solid-js";
function SearchSolid(props) {
const [query, setQuery] = createSignal("");
const filtered = createMemo(
() => props.items.filter(i => i.name.includes(query()))
);
createEffect(() => {
document.title = "Results: " + filtered().length;
});
return (
<div>
<input value={query()} onInput={e => setQuery(e.target.value)} />
<For each={filtered()}>
{(i) => <p>{i.name}</p>}
</For>
</div>
);
}最佳实践
遵循这些模式编写地道且高性能的 SolidJS 代码:
- 不要在函数参数级别解构 props。在 JSX 中使用 props.x 或用 splitProps/mergeProps 包装。
- 对昂贵的派生计算使用 createMemo,避免每次读取时重新计算。
- 对复杂嵌套状态优先使用存储而非多个信号,获得自动深度响应式。
- 对列表使用 For 组件而非 array.map,获得正确的键控更新和最小 DOM 操作。
- 使用 Show 进行条件渲染而非三元表达式,避免创建两个分支。
- 将 createEffect 调用放在组件级别,不要放在条件或循环内。
- 在效果中使用 onCleanup 正确清理订阅、定时器和事件监听器。
- 利用 Suspense 和 ErrorBoundary 进行声明式异步和错误处理。
- 同步设置多个信号时使用 batch() 避免中间更新。
- 在 SolidStart 路由中使用 lazy() 代码分割组件,减少初始包大小。
常见问题
SolidJS 用来做什么?
SolidJS 用于构建快速的交互式 Web 应用和用户界面。适合仪表盘、单页应用、实时数据展示、开发者工具等对性能和包体积要求高的项目。SolidStart 扩展到支持 SSR 和 API 路由的全栈应用。
SolidJS 和 React 有什么区别?
SolidJS 没有虚拟 DOM。组件在设置期间只运行一次而非每次状态变化都重新渲染。状态使用信号而非钩子。Solid 将 JSX 编译为直接 DOM 操作。因此运行时性能显著更好,包体积更小。
SolidJS 比 React 快吗?
是的。SolidJS 在 JS Framework Benchmark 等基准测试中始终优于 React,性能接近原生 JavaScript。优势来自细粒度响应式、无虚拟 DOM 开销和编译时优化。
SolidJS 中的信号是什么?
信号是 SolidJS 的核心响应式原语。通过 createSignal 创建,返回 getter 和 setter 函数。在响应式上下文中读取 getter 时自动追踪依赖,调用 setter 时只更新该信号的精确订阅者。
SolidJS 有类似 Next.js 的元框架吗?
有。SolidStart 是 SolidJS 的官方元框架,提供文件路由、流式 SSR、服务器函数、静态站点生成、API 路由和部署适配器。
SolidJS 可以使用 TypeScript 吗?
可以。SolidJS 有一流的 TypeScript 支持,所有核心 API 都有完整类型。JSX 类型系统映射到真实 DOM 元素类型。
如何从 React 迁移到 SolidJS?
将 useState 替换为 createSignal,useEffect 替换为 createEffect,useMemo 替换为 createMemo。移除所有依赖数组。不要解构 props,使用 Show 和 For 组件。移除 useCallback。
SolidJS 可以用于生产环境吗?
可以。SolidJS 在 2021 年达到 1.0 版本并保持稳定。eBay、Rakuten 和 Cloudflare 等公司在生产中使用。SolidStart 在 2024 年达到 1.0。生态系统包含成熟的路由、状态管理、UI 组件和测试库。