SolidJS 是一个基于细粒度响应式(fine-grained reactivity)的 JavaScript UI 框架,它不走虚拟 DOM 那一套,而是在编译期把状态和视图绑定到具体的 DOM 节点,从而在不牺牲开发体验的前提下拿到接近原生 JS 的性能。如果你已经熟悉 Vue3 的响应式系统 或 Next.js 的 React 渲染模型,那么理解 Solid 的”信号(Signal)”会非常快——但它解决问题的思路其实更彻底。
一、为什么需要细粒度响应式
主流框架里,React 靠组件级重渲染 + 虚拟 DOM diff 来更新 UI,Vue 靠依赖追踪做组件内精确更新。两者都足够好,但当状态嵌套很深、列表很长时,颗粒度仍然偏粗:一次 setState 往往要带着整棵子树重新计算。
Solid 的思路是:把响应式精确到”一个变量引用了一处 DOM 文本”。状态变化时,只有真正读取了这个变量的那一行 DOM 更新,其余纹丝不动。它用编译期转换实现了这一点,运行时几乎没有虚拟 DOM 的开销。
这也意味着 Solid 在”高频更新”场景下优势最明显:实时行情、协作白板的光标、大表格的滚动与筛选,这类界面在虚拟 DOM 方案里很容易因为 diff 成本被拖慢,而 Solid 只是安静地改掉少数几个文本节点,主线程几乎无感。
二、SolidJS 的核心模型
2.1 Signal:最小的响应式单元
Signal 是 Solid 的基石,由 createSignal 创建,返回一个读取函数(getter)和写入函数(setter)。注意读取时必须调用这个函数,Solid 才能建立依赖追踪。
import { createSignal } from "solid-js";
const [count, setCount] = createSignal(0);
console.log(count()); // 读取:0
setCount(count() + 1); // 写入:1
console.log(count()); // 1
2.2 派生与副作用:createMemo / createEffect
createMemo 用于派生状态(带缓存),createEffect 用于执行副作用。它们都自动收集依赖,依赖变化时重新运行。
import { createSignal, createMemo, createEffect } from "solid-js";
const [price, setPrice] = createSignal(10);
const [qty, setQty] = createSignal(2);
// 派生值,依赖 price/qty,仅在它们变化时重算
const total = createMemo(() => price() * qty());
createEffect(() => {
console.log("订单金额:", total());
});
setQty(3); // effect 自动触发,打印 30
2.3 Stores:嵌套状态管理
当状态是深层对象时,用 createStore 可以获得路径级的细粒度更新,而不是整体替换。
import { createStore } from "solid-js/store";
const [user, setUser] = createStore({
name: "Ada",
profile: { age: 30, city: "Hangzhou" },
});
// 只更新嵌套字段,视图里用到 user.profile.age 的地方才刷新
setUser("profile", "age", 31);
2.4 createResource:异步数据
UI 离不开异步请求。createResource 把”请求 + 加载态 + 错误态”封装成一个响应式数据源,配合 Suspense 边界可以很自然地做加载占位,而不必手写一堆 loading 标志位。
import { createResource, Suspense } from "solid-js";
const [user] = createResource(async (id) => {
const res = await fetch(`/api/users/${id}`);
return res.json();
});
// 配合 <Suspense> 在等待期间展示 fallback
<Suspense fallback={<p>加载中...</p>}>
<p>{user()?.name}</p>
</Suspense>
注意 createResource 返回的是 [source, { loading, error, latest }],读取用 source(),在 effect 或 JSX 里它同样会被纳入依赖追踪,请求结果一回来对应视图自动刷新。
三、与 React、Vue 的响应式差异
| 维度 | SolidJS | React | Vue 3 |
|---|---|---|---|
| 更新颗粒度 | 单个 DOM 节点 | 组件级(含 VDOM diff) | 组件内依赖级 |
| 虚拟 DOM | 无 | 有 | 有(编译优化) |
| 状态读取 | 调用 getter | 直接引用变量 | ref.value / 直接属性 |
| 心智模型 | 信号图 | 快照 + 重渲染 | 响应式代理 |
四、从零搭建一个 Solid 项目(Vite)
4.1 脚手架与依赖
# 使用官方 Vite 模板
npm create vite@latest my-solid-app -- --template solid-ts
cd my-solid-app
npm install
npm run dev
开发时推荐在 VS Code 中安装 Solid 官方扩展,获得模板语法高亮与 JSX 类型提示。配合 前端缓存与构建优化 技巧,首屏体验会更好。
4.2 第一个组件
Solid 的组件就是一个返回 JSX 的函数,且只执行一次——后续更新交给定向的 DOM 操作,而不是重新跑函数体。
import { createSignal, For } from "solid-js";
function Counter() {
const [n, setN] = createSignal(0);
return (
<div>
<button onClick={() => setN(n() + 1)}>+1</button>
<span>当前:{n()}</span>
</div>
);
}
4.3 列表渲染与事件
用 For 做 keyed 列表渲染,数据变动时只移动/更新受影响的节点,不会整体重建。
function TodoList() {
const [items, setItems] = createSignal(["买咖啡", "写代码"]);
return (
<ul>
<For each={items()}>{(item) => <li>{item}</li>}</For>
</ul>
);
}
五、性能实测:为什么更快
| 场景(10k 行列表更新) | SolidJS | React (VDOM) |
|---|---|---|
| 单次局部更新耗时 | ~0.3 ms | ~3–8 ms(含 diff) |
| 打包体积(含 runtime) | ~7 KB | ~45 KB |
| 内存常驻节点 | 仅真实 DOM | 真实 DOM + VDOM 树 |
数字来自社区常见基准测试,实际差异随场景浮动;但”无虚拟 DOM + 编译期定向更新”带来的开销优势在高频、大数据量 UI 上最明显。
当然,Solid 并非处处碾压:低频、以表单和静态展示为主的页面里,虚拟 DOM 的 diff 成本几乎可以忽略,此时框架间的差异主要体现在生态成熟度与团队习惯;而且 Solid 的编译式响应式对源码组织有一定约束,团队上手需要适应”调用 getter”和”使用控制流指令”这两个新习惯,否则很容易写出不更新的 bug。
六、与现有生态集成
Solid 不是孤岛:@solidjs/router 提供文件式与组件式路由,solid-meta 管理 SEO 头信息,solid-start(现为 SolidJS 官方元框架)支持 SSR/SSG,配合边缘运行时部署很顺手。状态管理上,除了内置 Store,也能接入 Zustand 风格的轻量方案。
七、三个常见踩坑
7.1 忘记”调用” getter
最常见的新手错误是在 JSX 里写 {count} 而不是 {count()}。前者把函数引用当成值直接渲染,永远不会更新;Solid 的响应式依赖建立在点”读取”这一刻,不调用就切断了追踪链,UI 自然静止。
7.2 在 effect 里订阅外部源却不清理
如果你在 createEffect 里订阅了事件总线、WebSocket 或定时器,组件销毁时必须用 onCleanup 取消订阅,否则会造成内存泄漏与重复触发。React 用户很容易沿用旧习惯而漏掉这一步。
7.3 把组件当函数反复调用
Solid 的组件函数只执行一次,因此不能在其中写 if/for 来控制渲染,而是要交给 Show、For 等控制流指令。把组件当成 React 那样”每次渲染重跑”来写逻辑,是第二大误区,也是多数”为什么不更新”问题的根因。
八、生产落地建议
- 新项目或重渲染敏感模块(仪表盘、表格、实时流)优先考虑 Solid,收益最大。
- 团队已深度绑定 React/Vue 生态时,可用 Solid 做性能瓶颈组件,而非整体重写。
- 务必遵守”读取即调用”约定,否则依赖追踪失效,UI 不刷新。
- SSR 场景记得区分只在服务端运行的副作用,避免内存泄漏。
- 借助 Vite 的极速 HMR,Solid 的开发体验非常顺滑,新项目建议直接采用官方
solid-ts模板起步。
九、小结
SolidJS 用编译期细粒度响应式把”状态→DOM”的链路压到了最短:没有虚拟 DOM,没有组件级重渲染,只有被读取的那一处节点在变。它语法贴近 React、性能贴近原生,是前端框架里非常值得一试的”新锐”。对新项目,尤其是高频更新的界面,Solid 值得放进你的选型清单。




