要点
观察一篇 SolidJS。定位一句话说完:用 JSX 写组件、靠细粒度响应式(fine-grained reactivity)直接更新 DOM 节点的框架——没有虚拟 DOM,组件函数从头到尾只运行一次。它常被称作”React 的形、Vue 的魂”:语法长得像 React,更新机制却完全是另一套。截至本文(2025 年 11 月),稳定线是 Solid 1.9.x(1.9 于 2024 年 10 月发布),配套元框架 SolidStart 1.0 已于 2024 年正式 GA。看完这篇你能回答四个问题:细粒度响应式到底细在哪、三个原语怎么用、它和 Svelte/Vue 的机制差异、以及生态规模的真实水位。
细粒度响应式:组件为什么只运行一次
先回忆 React 的模型:组件函数在每次状态变化后重跑,产出一棵新的虚拟 DOM 树,diff 之后再打补丁。Solid 把这条链砍掉了:信号(signal)是运行时的一等公民,订阅关系精确到表达式——模板里哪段文本读了哪个信号,编译后就是哪个文本节点的更新函数。状态变了,只有绑定了这个信号的那几个 DOM 节点被更新,组件函数本身不会再执行。
import { createSignal, createMemo, createEffect } from "solid-js";
export default function Counter() {
// 信号是取值函数:count 是函数,读值要调用 count(),这一点与 Svelte/Vue 都不同
const [count, setCount] = createSignal(0);
const doubled = createMemo(() => count() * 2);
createEffect(() => {
console.log(`count = ${count()}`); // 只追踪执行中实际读到的信号
});
// 组件函数只在首次渲染时执行一次,之后更新的是具体的 DOM 节点
return (
<button onClick={() => setCount(count() + 1)}>
{count()} 的两倍是 {doubled()}
</button>
);
}
JSX 语法本身是 React 时代定下的 JS 扩展,Solid 复用了这层语法外衣但编译目标完全不同(不走 React 的运行时);对 JSX 背后的 JS 语言细节想补课的,站内Node.js 与 JavaScript 语言特性详解通用。
三个原语与控制流
Solid 的响应式 API 面很小,核心就是三个原语:createSignal(状态)、createMemo(派生缓存)、createEffect(副作用)。没有 useState、useMemo、useEffect 之间的心智纠缠,因为组件只跑一次,不存在”依赖数组漏写导致闭包过期”的问题——依赖追踪是运行时真实发生的,不是靠你声明。
渲染列表与条件时用内置控制流组件,而不是 JS 表达式硬写:
<Show when={user()}>
<Profile user={user()} /> <!-- 条件为真才创建节点,切换时销毁/重建 -->
</Show>
<For each={items()}>
{(item) => <li>{item.name}</li>} <!-- 按引用追踪每项,重排时直接移动 DOM 节点 -->
</For>
没有虚拟 DOM 意味着没有 key diff:For 直接记住每项对应的节点,列表重排时移动现成的节点而不是重建。这也是 Solid 在长列表、频繁更新场景下表现好的机制根源(效果幅度以你自己的实测为准,本篇不引用具体数字)。
反模式点名:在组件参数里解构 props。这是 Solid 最著名的坑:
// 反模式:解构 props —— 解构发生在组件函数运行的一瞬间,拿到的是当时的快照
function User({ name }) { // 之后 props.name 再变化,这里不会更新
return <p>{name}</p>;
}
// 正解:完整接收 props,读值留在渲染表达式里,追踪才能保持
function User(props) {
return <p>{props.name}</p>;
}
原因在机制里:Solid 的 props 是带 getter 的对象,解构等于提前”取值”,把活的订阅变成了死的值。从 React 过来的人几乎都会踩一次——React 里解构 props 是被鼓励的写法,Solid 里是 bug 来源。
与 Svelte 5、Vue 3 的对照
本站上月写过《Svelte 5 入门:runes 带来的新心智》,两者同属”甩掉虚拟 DOM”路线,但一个靠编译器、一个靠运行时:
| 维度 | SolidJS | Svelte 5 | Vue 3 |
|---|---|---|---|
| 响应式核心 | 运行时信号,函数调用取值 | 编译期改写的 runes | Proxy 拦截的响应式对象 |
| 组件函数执行 | 仅一次,更新不重跑组件 | 编译后初始化一次,更新走定点函数 | 响应变化时重跑渲染 |
| DOM 更新方式 | 直接更新订阅该信号的节点 | 编译产物定点更新 | 虚拟 DOM 加依赖追踪的 patch |
| 模板语法 | JSX | 类 HTML 单文件组件 | 类 HTML 单文件组件 |
| 对 React 习惯的迁移 | 语法近乎无缝,机制需重学 | 语法与心智都要换 | 语法与心智都要换 |
值得注意的是方向趋同:Vue、Svelte、Solid 乃至 Angular 的 signals 提案都在往”信号与细粒度追踪”收敛,学懂 Solid 这套原语,看懂其他家的响应式设计几乎是免费的。
SolidStart 与生态规模的诚实评估
元框架 SolidStart 提供 JSX 组件、文件路由、服务端渲染与流式输出,定位对应 Next.js/SvelteKit,异步数据用内置的 createResource 处理——Promise 与 async/await 基础不牢的先补站内Node.js 异步与 Promise。但它 2024 年才 GA,成熟度与 Next.js/SvelteKit 不在一个阶段,选型时按”年轻一年半”心理预期打折。
生态规模必须说实话:Solid 的 npm 下载量、组件库数量、中文资料与招聘岗位,和 React/Vue 不是一个体量(本篇不给具体数字,以 npm 与招聘平台的实时数据为准)。头部 UI 库有选择但不算丰富,遇到冷门问题搜到的多半是英文讨论;社区质量高、核心 API 多年稳定(1.x 线),但”资料少、招人难”是真实约束。UI 库层面的缺口,实际项目里常用 TailwindCSS 这类 CSS 方案加自建组件补齐。
要不要现在投入
诚实的判断分三种情况。作为学习投入:非常值——三个原语一下午能吃透,而它教给你的响应式模型是整个行业正在收敛的方向,这份认知迁移到 Vue/Svelte/Angular 都保值。作为个人项目:可以用,核心库稳定、开发体验好,性能敏感的交互密集型面板是甜区;代价是踩坑时能搜到的中文资料有限。作为团队生产主力栈:现阶段不推荐——招人、资料、三方库、SSR 生态的成熟度都还撑不起这个决定,除非团队本来就小而强、且对性能有明确诉求。存量 React/Vue 项目没有迁移的理由,Solid 的价值在于”下一个新项目多一个选项”,以及学习本身的回报。
延伸阅读
机制上与本文最互补的是上月的《Svelte 5 入门:runes 带来的新心智》(上文已链接)——编译时与运行时两条路实现同一个目标,对读能把”细粒度响应式”这个词真正吃透;JSX 与异步的基础补课见文中两篇 Node.js 链接。框架选型的横向视角,等站内后续的框架对比篇。
版本核实(以官方文档为准):Solid 稳定线为 1.9.x(1.9 发布于 2024-10,此前 1.8 为 2023-10),核心 API 为 createSignal/createMemo/createEffect/createResource 与 Show/For 控制流组件;元框架 SolidStart 1.0 已于 2024 年正式 GA(官方博客公告)。安装命令均不钉版本号,以 solidjs.com 当前文档为准。