首页
学习
活动
专区
圈层
工具
发布

Solid

修改于 2026-09-30 15:11:24
15
概述

Solid 是一个用于构建用户界面的开源 JavaScript 前端框架,由 Ryan Carniato 创建并以 MIT 协议开源。它以细粒度响应式(Fine-grained Reactivity)和信号(Signals)机制为核心设计理念,将 JSX 直接编译为真实的 DOM 操作,不依赖虚拟 DOM 与差异比对(diffing),从而在渲染性能和包体积上具备显著优势。Solid 的 API 在写法上与 React 函数组件相似,但组件函数只在首次渲染时执行一次,响应式更新通过信号驱动的依赖追踪精确完成。经过多年迭代,Solid 已发展出包含 SolidStart 元框架、stores 状态管理库在内的完整生态,其核心思想也被 React、Vue、Angular 等主流框架在不同程度上借鉴。

一、Solid 的核心设计理念是什么?

1. 细粒度响应式优先

Solid 的核心设计围绕"细粒度响应式"展开。当状态发生变化时,框架只更新真正依赖该状态的精确 DOM 节点(如某个文本节点或属性),而不是重新渲染整个组件或组件子树。这种机制让框架"只做必要的更新",避免了大范围的重渲染开销。

2. 组件即执行一次

在 Solid 中,组件函数只在首次挂载时执行一次,而非每次状态变化都重新执行。响应式绑定(如信号)在首次执行时建立依赖追踪关系,后续状态变化时由框架直接更新对应的 DOM 绑定,无需重新运行组件逻辑。

3. 编译期优化

Solid 通过编译器将 JSX 模板转换为真实的 DOM 创建与绑定操作,在编译阶段就确定好 DOM 结构,运行时无需额外的虚拟 DOM 构建与协调(reconciliation)成本。

4. 熟悉而克制的 API

Solid 在写法上借鉴了 React 的函数组件与单向数据流理念,同时吸收了 Knockout 的响应式思想,力求让开发者"上手熟悉、心智负担低"。它没有 Hook 规则约束,读写分离,接口保持不可变(immutable)。

二、Solid 的信号(Signals)机制是什么?

1. 信号的定义

信号(Signal)是 Solid 响应式系统的基本单元,是一个带有读写能力的可观察值。通过 createSignal 创建信号后,读取其值(调用 getter)会自动建立依赖,写入其值(调用 setter)会通知所有依赖它的计算和绑定进行更新。

2. 读写分离

Solid 采用读写分离模型:读取信号值与写入信号值是独立的操作。这种设计让依赖追踪在读取时自动完成,而不需要开发者手动声明依赖,减少了心智负担和出错概率。

3. 派生信号与计算

基于信号可以构建派生状态,如通过 createMemo 创建的记忆化计算值。当上游信号变化时,派生计算会自动重新求值,且只有真正依赖它的地方才会被触发更新。

4. 信号的可组合性

信号可以在业务逻辑与 JSX 视图之间自由组合使用,从数据层到 DOM 绑定层都能精确控制更新范围,这正是 Solid"用更少做更多"的能力来源。

三、Solid 的细粒度响应式是如何工作的?

1. 依赖追踪

当组件首次执行、读取某个信号的值时,Solid 会记录"当前这个计算/绑定依赖了哪些信号",建立起信号与消费者之间的依赖关系图。

2. 精确更新

当信号的值发生变化时,框架沿着依赖图找到所有直接依赖该信号的消费者,并只更新这些消费者对应的精确 DOM 节点,不会触碰任何无关节点。

3. 无虚拟 DOM 的更新路径

由于 JSX 在编译期已被转换为真实的 DOM 创建与绑定代码,更新时框架直接对目标 DOM 节点进行写入,不存在"构建虚拟 DOM 树 → 差异比对 → 应用补丁"的中间过程,因此更新路径更短、开销更低。

4. 批量调度

Solid 会对同一轮内的多次信号写入进行批处理,合并为一次更新调度,避免频繁的微更新造成的性能损耗。在 Solid 2.0 中,更新采用微任务(microtask)级别的确定性批处理,读取在批处理刷新前不会更新。

四、Solid 为什么不使用虚拟 DOM?

1. 虚拟 DOM 的代价

虚拟 DOM 需要为每次渲染构建一棵内存中的节点树,并与旧树进行差异比对,再将差异应用到真实 DOM。这一过程本身会消耗 CPU 和内存,在高频更新场景下尤为明显。

2. 编译期即可确定 DOM 结构

Solid 认为,借助编译器在构建阶段就能确定 JSX 对应的真实 DOM 结构,运行时再走一遍"虚拟 DOM + diff"属于重复劳动。因此它选择把 DOM 结构在编译期固化,运行时直接操作真实节点。

3. 细粒度更新替代整体 diff

通过信号驱动的依赖追踪,Solid 在更新时已经明确知道"哪些节点需要改",无需通过 diff 去"找出"变化。这从根本上省去了 diff 环节,同时保证更新粒度更细。

4. 性能与体积收益

去掉虚拟 DOM 后,Solid 的核心运行时体积大幅缩小(gzip 后约 7.6 KB),并在主流性能基准测试中长期位居第一梯队,渲染与内存占用表现接近原生 JavaScript。

五、Solid 的 JSX 编译机制是怎样的?

1. JSX 转换为真实 DOM 操作

Solid 的编译器将 JSX 模板直接转换为创建真实 DOM 节点、并建立响应式绑定的代码。例如动态文本会被编译为"创建文本节点 + 绑定信号"的形式,而非生成虚拟 DOM 描述对象。

2. 绑定而非重渲染

编译产物中,动态部分被替换为对具体 DOM 节点的引用和更新函数。当依赖的信号变化时,只调用对应的更新函数写入该节点,组件函数本身不会重新执行。

3. 编译工具链

Solid 官方提供 Vite 插件(@solidjs/vite-plugin)集成编译能力。在 Solid 2.0 中,编译工具链基于 Rust 与 Oxc 重写,相比传统的 Babel 方案在大型项目上可获得数十倍到数百倍的编译速度提升,同时保持零配置、无需迁移。Babel 预设仍然可用。

六、Solid 的组件生命周期是怎样的?

1. 单次执行模型

Solid 组件函数在挂载时执行一次,用于建立响应式绑定和初始渲染。此后状态变化不再重新执行组件函数,而是由框架直接更新受影响的 DOM 绑定。

2. 生命周期钩子

Solid 提供 onMount、onCleanup、onError 等钩子来管理副作用与清理逻辑。在 Solid 2.0 中,onMount 被 onSettled 取代(可返回清理函数),用于在组件稳定后执行逻辑。

3. 副作用与响应式作用域

通过 createEffect 可以在响应式作用域中运行副作用,当依赖的信号变化时自动重新执行,并在重新执行前运行上一次的清理函数,适合处理订阅、定时器、DOM 操作等场景。

七、Solid 的 createSignal 和 createMemo 有什么区别?

1. createSignal:基础可写状态

createSignal 创建一个可读写的信号,是 Solid 中最基础的状态单元。调用其 getter 读取当前值并建立依赖,调用其 setter 写入新值并触发依赖更新。

2. createMemo:只读派生计算

createMemo 创建一个只读的记忆化计算值,其值由传入的函数根据依赖的信号推导得出。它不会引入新的可写状态,而是对已有状态进行派生,且只有当依赖发生变化时才会重新求值。

3. 用途差异

createSignal 用于承载"源头状态"(如用户输入、开关值),createMemo 用于承载"由源头派生出的结果"(如格式化后的文本、过滤后的列表)。二者配合可实现读写分离、按需计算的响应式数据流。

八、Solid 的 createEffect 有什么作用?

1. 响应式副作用

createEffect 用于在响应式作用域中执行副作用逻辑。它会在首次运行时自动追踪内部读取的信号,当这些信号变化时重新执行,并在每次重新执行前运行清理函数。

2. 自动清理

createEffect 支持返回清理函数,在副作用重新执行或组件卸载时调用,适合处理事件监听、订阅、定时器等需要成对创建与销毁的资源,避免内存泄漏。

3. 2.0 的分阶段设计

在 Solid 2.0 中,createEffect 被拆分为"计算(compute)"与"应用(apply)"两个阶段,将依赖追踪与副作用执行分离,使更新能够被更合理地分组,以获得更优的批处理性能。

九、Solid 的异步处理机制是怎样的?

1. 1.x 的手动处理

在 Solid 1.x 中,异步数据获取通常需要在 createEffect 或外部状态管理中手动处理 Promise,并配合 createResource 等原语管理加载状态。

2. 2.0 的一等异步支持

Solid 2.0 将异步作为响应式系统的一等特性:计算(如 createMemo)可以直接返回 Promise 或异步迭代器,响应式图会自动挂起对应节点、等待结果解析后再恢复,无需开发者手动包裹 await 或维护加载状态。

3. 乐观更新与变更

Solid 2.0 提供 action(...) 与 createOptimisticStore,用于统一处理"乐观更新 → 等待服务端写入 → 重新校验"的变更流程,让乐观 UI 与服务端写操作形成连贯的数据流。

4. 确定性批处理

2.0 的更新采用微任务级别的确定性批处理,所有读取会延迟到微任务队列结束,需要立即读取当前值时可显式调用 flush()。

十、Solid 的 Suspense 机制是如何工作的?

1. 挂起与回退

Suspense(在 Solid 中通过 Loading 等边界组件体现)用于在某个子树尚无法产出 UI(如等待异步数据)时显示回退内容(fallback),待数据就绪后再展示真实内容。

2. 2.0 的重新设计

在 Solid 2.0 中,Loading 边界只在子树的初始加载阶段显示回退内容;后续的数据刷新会保持 UI 稳定,不再整体拆除并重建子树,从而避免"闪烁加载"带来的观感割裂。

3. 与异步图结合

由于异步值直接存在于响应式图中,Suspense 能够精确感知"哪个子树在等待、等待的是哪一次请求",实现更细粒度、更自然的加载态表达。

十一、Solid 如何实现服务端渲染(SSR)?

1. 同构渲染能力

Solid 支持服务端渲染,可在服务端预先生成 HTML 字符串发送给客户端,再在客户端进行水合(hydration),使页面首屏更快、更利于 SEO。

2. 流式 SSR 与流式水合

Solid 支持流式渲染(Streaming SSR)与流式水合,边生成边传输,减少首字节等待时间,提升大页面的加载体验。

3. SolidStart 元框架

SolidStart 是 Solid 官方的全栈元框架,提供基于文件的路由、服务端渲染、服务端函数(RPC 风格的远程调用)等能力。其 2.0 版本已移除早期的 Vinxi 构建层,直接基于 Vite 8 的 Environment API。随着 Solid 2.0 将服务端函数、服务层与文件路由等能力折叠进核心库,SolidStart 已被宣布退役并进入维护模式,其职责由 @solidjs/vite-plugin 的 start 模式承接。

十二、Solid 的性能表现如何?

1. 渲染性能

Solid 长期位居 js-framework-benchmark 等主流性能基准测试的第一梯队,渲染与内存占用表现接近原生 JavaScript。在第三方测试中,Solid 渲染 1000 行表格的耗时约 22.7 毫秒,同等条件下 React 约 25.6 毫秒。

2. 包体积

Solid 的核心运行时体积很小,gzip 后约 7.6 KB。在生产应用的对比中,实现同等功能时 Solid 的主包体积明显小于 React——有研究测得 Solid 主 JS 块约 27 KB,React 约 91 KB。

3. 性能优势的根源

这些优势直接源于 Solid 的架构选择:无虚拟 DOM、无 diff、JSX 编译为真实 DOM 绑定、信号驱动的细粒度更新,使框架在客户端和服务端都能保持高效。

十三、Solid 如何实现状态管理?

1. 基于信号的响应式状态

Solid 的状态管理建立在信号之上,通过 createSignal、createMemo、createEffect 等原语组织数据流,读写分离、依赖自动追踪,无需额外的订阅样板代码。

2. 局部与共享状态

组件内部可使用局部信号管理私有状态;跨组件共享状态则可通过 Context、提供的 store 方案或全局信号实现,保持数据流的单向与可预测。

3. 派生状态优先

Solid 鼓励"能派生就不额外存状态"的思路,通过 createMemo 从源头状态推导出展示数据,减少冗余状态、降低状态不一致的风险。

十四、Solid 的 stores 库是如何工作的?

1. createStore 与响应式对象

Solid 提供 createStore 用于创建深层响应式的状态对象,可方便地管理结构化状态,读写时同样遵循信号驱动的依赖追踪。

2. 2.0 的草稿式写入

在 Solid 2.0 中,store 的 setter 默认采用"草稿优先(draft-first)"语义:更新时传入一个可原地修改的草稿对象,框架据此生成新的状态,使嵌套状态的更新更直观。若需要旧的路径式写法,可使用 storePath(...) 作为可选辅助。

3. 与乐观更新结合

通过 createOptimisticStore,stores 可与服务端写入流程结合,先乐观地更新本地状态以即时反馈 UI,再在服务端确认后重新校验,形成一致的变更体验。

十五、Solid 如何实现错误边界(Error Boundaries)?

1. 错误捕获

Solid 提供错误边界能力,可在组件子树中捕获渲染或生命周期内抛出的异常,避免单个组件的错误导致整个应用崩溃。

2. 与 onError 钩子配合

通过 onError 等钩子可以注册错误处理逻辑,配合回退 UI 展示友好的错误提示,并可将错误上报到监控服务。

3. 边界隔离

错误边界将可能出错的逻辑限制在局部子树内,其余部分继续正常渲染,提升了应用的健壮性与用户体验。

十六、Solid 适合哪些项目类型和适用场景?

1. 对性能与体积敏感的应用

Solid 特别适合对首屏加载、运行时性能和包体积有较高要求的项目,如实时数据看板、交互式工具、内容密集型页面等。其细粒度响应式与极小的运行时体积在这些场景下优势明显。

2. 中小型与个人项目

在个人项目或中小商业项目中,若团队可以自主选择技术栈、且不强依赖庞大的现成组件生态,Solid 是一个成熟且有充分理由的选择,能带来更快的加载与更流畅的交互。

3. 需要服务端渲染的场景

借助 SolidStart,Solid 也适用于需要 SSR、SEO 的内容型站点与全栈应用,兼顾首屏速度与搜索引擎可见性。

4. 需要权衡的情况

若项目属于大型团队、高度依赖丰富的现成集成方案、需要 React Native 等跨端能力,或需要庞大的招聘市场支撑,则 React 等生态更成熟的框架仍是更稳妥的选择。Solid 的招聘池与生态规模相对较小,是需要长期考量的因素。

十七、Solid 与 React 有什么本质区别?

1. 渲染模型不同

React 采用虚拟 DOM 与协调(reconciliation)机制,组件在状态变化时会重新执行并进行 diff;Solid 不使用虚拟 DOM,JSX 编译为真实 DOM 绑定,组件函数只执行一次,更新时直接写入受影响的节点。

2. 响应式粒度不同

React 的更新以组件/子树为单位触发重渲染;Solid 以信号为单位进行细粒度更新,只更新精确依赖该信号的 DOM 节点。

3. 状态与副作用模型不同

React 通过 useState、useEffect 等 Hook 管理状态与副作用,并受 Hook 规则约束;Solid 使用信号与 createEffect 等原语,读写分离、无 Hook 规则,依赖追踪在读取时自动完成。

4. 生态与采用度差异

React 拥有最庞大的生态、组件库与招聘市场,是当前的主流选择;Solid 在性能与体积上更优,但生态规模和人才供给相对有限。二者是"技术表现"与"工程生态"之间的典型权衡。

十八、Solid 与 Vue 有什么区别?

1. 响应式实现路径

Vue 3 基于响应式代理(Proxy)实现依赖追踪,并默认使用虚拟 DOM;Solid 从设计之初就以信号为一等公民,且不使用虚拟 DOM。

2. 模板与渲染

Vue 使用模板(Template)语法并结合编译器优化渲染;Solid 直接使用 JSX,并将其编译为真实 DOM 操作。

3. 组件执行方式

Vue 组件在更新时会重新执行渲染函数生成虚拟 DOM;Solid 组件只执行一次,更新由信号驱动精确完成。

4. 演进方向

值得注意的是,Vue 在后续版本中引入的 Vapor Mode 等优化也在探索"跳过虚拟 DOM、直接编译为 DOM 操作"的方向,说明 Solid 的细粒度响应式理念正被更广泛地借鉴。

十九、Solid 与 Svelte 有什么区别?

1. 编译时机与方式

Svelte 采用编译时框架的思路,在构建阶段将组件编译为高效的命令式 DOM 操作代码,运行时几乎不保留框架层;Solid 则保留一个轻量的响应式运行时,通过信号与细粒度更新驱动渲染。

2. 响应式触发机制

Svelte 通过编译器分析赋值语句来触发更新(如 Svelte 5 的 runes,如 state、state、derived);Solid 在运行时通过信号的读写自动追踪依赖并更新。

3. 运行时体积与灵活性

两者都追求高性能与小体积。Svelte 的运行时更"薄",语法更接近原生 JavaScript;Solid 保留了响应式运行时,在复杂交互与状态组合上提供更明确的编程模型。二者都是"无虚拟 DOM"高性能阵营的重要代表。

相关文章
  • SOLID之SRP
    683
  • SOLID总结
    991
  • SOLID之ISP
    959
  • 改变shape solid color
    589
  • SOLID之DIP
    619
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
领券