Solid 是一个用于构建用户界面的开源 JavaScript 前端框架,由 Ryan Carniato 创建并以 MIT 协议开源。它以细粒度响应式(Fine-grained Reactivity)和信号(Signals)机制为核心设计理念,将 JSX 直接编译为真实的 DOM 操作,不依赖虚拟 DOM 与差异比对(diffing),从而在渲染性能和包体积上具备显著优势。Solid 的 API 在写法上与 React 函数组件相似,但组件函数只在首次渲染时执行一次,响应式更新通过信号驱动的依赖追踪精确完成。经过多年迭代,Solid 已发展出包含 SolidStart 元框架、stores 状态管理库在内的完整生态,其核心思想也被 React、Vue、Angular 等主流框架在不同程度上借鉴。
Solid 的核心设计围绕"细粒度响应式"展开。当状态发生变化时,框架只更新真正依赖该状态的精确 DOM 节点(如某个文本节点或属性),而不是重新渲染整个组件或组件子树。这种机制让框架"只做必要的更新",避免了大范围的重渲染开销。
在 Solid 中,组件函数只在首次挂载时执行一次,而非每次状态变化都重新执行。响应式绑定(如信号)在首次执行时建立依赖追踪关系,后续状态变化时由框架直接更新对应的 DOM 绑定,无需重新运行组件逻辑。
Solid 通过编译器将 JSX 模板转换为真实的 DOM 创建与绑定操作,在编译阶段就确定好 DOM 结构,运行时无需额外的虚拟 DOM 构建与协调(reconciliation)成本。
Solid 在写法上借鉴了 React 的函数组件与单向数据流理念,同时吸收了 Knockout 的响应式思想,力求让开发者"上手熟悉、心智负担低"。它没有 Hook 规则约束,读写分离,接口保持不可变(immutable)。
信号(Signal)是 Solid 响应式系统的基本单元,是一个带有读写能力的可观察值。通过 createSignal 创建信号后,读取其值(调用 getter)会自动建立依赖,写入其值(调用 setter)会通知所有依赖它的计算和绑定进行更新。
Solid 采用读写分离模型:读取信号值与写入信号值是独立的操作。这种设计让依赖追踪在读取时自动完成,而不需要开发者手动声明依赖,减少了心智负担和出错概率。
基于信号可以构建派生状态,如通过 createMemo 创建的记忆化计算值。当上游信号变化时,派生计算会自动重新求值,且只有真正依赖它的地方才会被触发更新。
信号可以在业务逻辑与 JSX 视图之间自由组合使用,从数据层到 DOM 绑定层都能精确控制更新范围,这正是 Solid"用更少做更多"的能力来源。
当组件首次执行、读取某个信号的值时,Solid 会记录"当前这个计算/绑定依赖了哪些信号",建立起信号与消费者之间的依赖关系图。
当信号的值发生变化时,框架沿着依赖图找到所有直接依赖该信号的消费者,并只更新这些消费者对应的精确 DOM 节点,不会触碰任何无关节点。
由于 JSX 在编译期已被转换为真实的 DOM 创建与绑定代码,更新时框架直接对目标 DOM 节点进行写入,不存在"构建虚拟 DOM 树 → 差异比对 → 应用补丁"的中间过程,因此更新路径更短、开销更低。
Solid 会对同一轮内的多次信号写入进行批处理,合并为一次更新调度,避免频繁的微更新造成的性能损耗。在 Solid 2.0 中,更新采用微任务(microtask)级别的确定性批处理,读取在批处理刷新前不会更新。
虚拟 DOM 需要为每次渲染构建一棵内存中的节点树,并与旧树进行差异比对,再将差异应用到真实 DOM。这一过程本身会消耗 CPU 和内存,在高频更新场景下尤为明显。
Solid 认为,借助编译器在构建阶段就能确定 JSX 对应的真实 DOM 结构,运行时再走一遍"虚拟 DOM + diff"属于重复劳动。因此它选择把 DOM 结构在编译期固化,运行时直接操作真实节点。
通过信号驱动的依赖追踪,Solid 在更新时已经明确知道"哪些节点需要改",无需通过 diff 去"找出"变化。这从根本上省去了 diff 环节,同时保证更新粒度更细。
去掉虚拟 DOM 后,Solid 的核心运行时体积大幅缩小(gzip 后约 7.6 KB),并在主流性能基准测试中长期位居第一梯队,渲染与内存占用表现接近原生 JavaScript。
Solid 的编译器将 JSX 模板直接转换为创建真实 DOM 节点、并建立响应式绑定的代码。例如动态文本会被编译为"创建文本节点 + 绑定信号"的形式,而非生成虚拟 DOM 描述对象。
编译产物中,动态部分被替换为对具体 DOM 节点的引用和更新函数。当依赖的信号变化时,只调用对应的更新函数写入该节点,组件函数本身不会重新执行。
Solid 官方提供 Vite 插件(@solidjs/vite-plugin)集成编译能力。在 Solid 2.0 中,编译工具链基于 Rust 与 Oxc 重写,相比传统的 Babel 方案在大型项目上可获得数十倍到数百倍的编译速度提升,同时保持零配置、无需迁移。Babel 预设仍然可用。
Solid 组件函数在挂载时执行一次,用于建立响应式绑定和初始渲染。此后状态变化不再重新执行组件函数,而是由框架直接更新受影响的 DOM 绑定。
Solid 提供 onMount、onCleanup、onError 等钩子来管理副作用与清理逻辑。在 Solid 2.0 中,onMount 被 onSettled 取代(可返回清理函数),用于在组件稳定后执行逻辑。
通过 createEffect 可以在响应式作用域中运行副作用,当依赖的信号变化时自动重新执行,并在重新执行前运行上一次的清理函数,适合处理订阅、定时器、DOM 操作等场景。
createSignal 创建一个可读写的信号,是 Solid 中最基础的状态单元。调用其 getter 读取当前值并建立依赖,调用其 setter 写入新值并触发依赖更新。
createMemo 创建一个只读的记忆化计算值,其值由传入的函数根据依赖的信号推导得出。它不会引入新的可写状态,而是对已有状态进行派生,且只有当依赖发生变化时才会重新求值。
createSignal 用于承载"源头状态"(如用户输入、开关值),createMemo 用于承载"由源头派生出的结果"(如格式化后的文本、过滤后的列表)。二者配合可实现读写分离、按需计算的响应式数据流。
createEffect 用于在响应式作用域中执行副作用逻辑。它会在首次运行时自动追踪内部读取的信号,当这些信号变化时重新执行,并在每次重新执行前运行清理函数。
createEffect 支持返回清理函数,在副作用重新执行或组件卸载时调用,适合处理事件监听、订阅、定时器等需要成对创建与销毁的资源,避免内存泄漏。
在 Solid 2.0 中,createEffect 被拆分为"计算(compute)"与"应用(apply)"两个阶段,将依赖追踪与副作用执行分离,使更新能够被更合理地分组,以获得更优的批处理性能。
在 Solid 1.x 中,异步数据获取通常需要在 createEffect 或外部状态管理中手动处理 Promise,并配合 createResource 等原语管理加载状态。
Solid 2.0 将异步作为响应式系统的一等特性:计算(如 createMemo)可以直接返回 Promise 或异步迭代器,响应式图会自动挂起对应节点、等待结果解析后再恢复,无需开发者手动包裹 await 或维护加载状态。
Solid 2.0 提供 action(...) 与 createOptimisticStore,用于统一处理"乐观更新 → 等待服务端写入 → 重新校验"的变更流程,让乐观 UI 与服务端写操作形成连贯的数据流。
2.0 的更新采用微任务级别的确定性批处理,所有读取会延迟到微任务队列结束,需要立即读取当前值时可显式调用 flush()。
Suspense(在 Solid 中通过 Loading 等边界组件体现)用于在某个子树尚无法产出 UI(如等待异步数据)时显示回退内容(fallback),待数据就绪后再展示真实内容。
在 Solid 2.0 中,Loading 边界只在子树的初始加载阶段显示回退内容;后续的数据刷新会保持 UI 稳定,不再整体拆除并重建子树,从而避免"闪烁加载"带来的观感割裂。
由于异步值直接存在于响应式图中,Suspense 能够精确感知"哪个子树在等待、等待的是哪一次请求",实现更细粒度、更自然的加载态表达。
Solid 支持服务端渲染,可在服务端预先生成 HTML 字符串发送给客户端,再在客户端进行水合(hydration),使页面首屏更快、更利于 SEO。
Solid 支持流式渲染(Streaming SSR)与流式水合,边生成边传输,减少首字节等待时间,提升大页面的加载体验。
SolidStart 是 Solid 官方的全栈元框架,提供基于文件的路由、服务端渲染、服务端函数(RPC 风格的远程调用)等能力。其 2.0 版本已移除早期的 Vinxi 构建层,直接基于 Vite 8 的 Environment API。随着 Solid 2.0 将服务端函数、服务层与文件路由等能力折叠进核心库,SolidStart 已被宣布退役并进入维护模式,其职责由 @solidjs/vite-plugin 的 start 模式承接。
Solid 长期位居 js-framework-benchmark 等主流性能基准测试的第一梯队,渲染与内存占用表现接近原生 JavaScript。在第三方测试中,Solid 渲染 1000 行表格的耗时约 22.7 毫秒,同等条件下 React 约 25.6 毫秒。
Solid 的核心运行时体积很小,gzip 后约 7.6 KB。在生产应用的对比中,实现同等功能时 Solid 的主包体积明显小于 React——有研究测得 Solid 主 JS 块约 27 KB,React 约 91 KB。
这些优势直接源于 Solid 的架构选择:无虚拟 DOM、无 diff、JSX 编译为真实 DOM 绑定、信号驱动的细粒度更新,使框架在客户端和服务端都能保持高效。
Solid 的状态管理建立在信号之上,通过 createSignal、createMemo、createEffect 等原语组织数据流,读写分离、依赖自动追踪,无需额外的订阅样板代码。
组件内部可使用局部信号管理私有状态;跨组件共享状态则可通过 Context、提供的 store 方案或全局信号实现,保持数据流的单向与可预测。
Solid 鼓励"能派生就不额外存状态"的思路,通过 createMemo 从源头状态推导出展示数据,减少冗余状态、降低状态不一致的风险。
Solid 提供 createStore 用于创建深层响应式的状态对象,可方便地管理结构化状态,读写时同样遵循信号驱动的依赖追踪。
在 Solid 2.0 中,store 的 setter 默认采用"草稿优先(draft-first)"语义:更新时传入一个可原地修改的草稿对象,框架据此生成新的状态,使嵌套状态的更新更直观。若需要旧的路径式写法,可使用 storePath(...) 作为可选辅助。
通过 createOptimisticStore,stores 可与服务端写入流程结合,先乐观地更新本地状态以即时反馈 UI,再在服务端确认后重新校验,形成一致的变更体验。
Solid 提供错误边界能力,可在组件子树中捕获渲染或生命周期内抛出的异常,避免单个组件的错误导致整个应用崩溃。
通过 onError 等钩子可以注册错误处理逻辑,配合回退 UI 展示友好的错误提示,并可将错误上报到监控服务。
错误边界将可能出错的逻辑限制在局部子树内,其余部分继续正常渲染,提升了应用的健壮性与用户体验。
Solid 特别适合对首屏加载、运行时性能和包体积有较高要求的项目,如实时数据看板、交互式工具、内容密集型页面等。其细粒度响应式与极小的运行时体积在这些场景下优势明显。
在个人项目或中小商业项目中,若团队可以自主选择技术栈、且不强依赖庞大的现成组件生态,Solid 是一个成熟且有充分理由的选择,能带来更快的加载与更流畅的交互。
借助 SolidStart,Solid 也适用于需要 SSR、SEO 的内容型站点与全栈应用,兼顾首屏速度与搜索引擎可见性。
若项目属于大型团队、高度依赖丰富的现成集成方案、需要 React Native 等跨端能力,或需要庞大的招聘市场支撑,则 React 等生态更成熟的框架仍是更稳妥的选择。Solid 的招聘池与生态规模相对较小,是需要长期考量的因素。
React 采用虚拟 DOM 与协调(reconciliation)机制,组件在状态变化时会重新执行并进行 diff;Solid 不使用虚拟 DOM,JSX 编译为真实 DOM 绑定,组件函数只执行一次,更新时直接写入受影响的节点。
React 的更新以组件/子树为单位触发重渲染;Solid 以信号为单位进行细粒度更新,只更新精确依赖该信号的 DOM 节点。
React 通过 useState、useEffect 等 Hook 管理状态与副作用,并受 Hook 规则约束;Solid 使用信号与 createEffect 等原语,读写分离、无 Hook 规则,依赖追踪在读取时自动完成。
React 拥有最庞大的生态、组件库与招聘市场,是当前的主流选择;Solid 在性能与体积上更优,但生态规模和人才供给相对有限。二者是"技术表现"与"工程生态"之间的典型权衡。
Vue 3 基于响应式代理(Proxy)实现依赖追踪,并默认使用虚拟 DOM;Solid 从设计之初就以信号为一等公民,且不使用虚拟 DOM。
Vue 使用模板(Template)语法并结合编译器优化渲染;Solid 直接使用 JSX,并将其编译为真实 DOM 操作。
Vue 组件在更新时会重新执行渲染函数生成虚拟 DOM;Solid 组件只执行一次,更新由信号驱动精确完成。
值得注意的是,Vue 在后续版本中引入的 Vapor Mode 等优化也在探索"跳过虚拟 DOM、直接编译为 DOM 操作"的方向,说明 Solid 的细粒度响应式理念正被更广泛地借鉴。
Svelte 采用编译时框架的思路,在构建阶段将组件编译为高效的命令式 DOM 操作代码,运行时几乎不保留框架层;Solid 则保留一个轻量的响应式运行时,通过信号与细粒度更新驱动渲染。
Svelte 通过编译器分析赋值语句来触发更新(如 Svelte 5 的 runes,如 state、state、derived);Solid 在运行时通过信号的读写自动追踪依赖并更新。
两者都追求高性能与小体积。Svelte 的运行时更"薄",语法更接近原生 JavaScript;Solid 保留了响应式运行时,在复杂交互与状态组合上提供更明确的编程模型。二者都是"无虚拟 DOM"高性能阵营的重要代表。