跳转到内容

SolidJS — 细粒度响应式 UI 框架

是什么

SolidJS 是一个**用 React 风格 JSX 但没有”重新渲染”**的前端框架。日常类比:React 是数据变了把整张菜单重写一遍再贴回墙上;Solid 是数据变了只画掉那一行换一笔——其它行根本没动过。

更技术一点说:你写一个组件函数,它一辈子只跑一次。函数里读到的”数据”实际上是一个个信号(signal),信号变了,框架直接更新订阅它的那一小段 DOM——不重跑组件、不做 virtual DOM diff。

function Hello() {
const [name, setName] = createSignal('Jason')
return <h1>你好,{name()}</h1>
}

看起来和 React 几乎一样,但语义完全相反——Hello 只跑一次,name() 才是真正的订阅入口。

为什么重要

不理解 Solid,下面这些事都没法解释:

  • 比 React 快 1.5-3 倍:JS Framework Benchmark 长期前三,背后是”细粒度反应式 + 无 virtual DOM diff”两个机制叠加
  • JSX 看起来像 React 但底层完全不同:组件函数只跑一次这个事实改写了所有”重渲染心智”,是理解现代响应式框架的钥匙
  • 启发了 React Compiler:React 团队 2024 年推出的 React Compiler 借鉴了 Solid 编译期细粒度追踪的思路
  • SolidStart 是 Next.js 的有力替代:同样是 SSR + 路由 + 数据加载,但 runtime 体积小得多

核心要点

Solid 的心智模型可以拆成 三块

  1. 信号(Signal):存”会变的值”的最小单位。createSignal(0) 返回一对 getter / setter——count() 读,setCount(1) 写。读的时候自动登记自己是订阅者。类比:广播电台和收音机——按下一个频道(调用 getter)就自动收到后续广播。

  2. 派生(Effect / Memo)createEffect 跑副作用(拉数据、写日志、操作 DOM 之外的事),createMemo 算派生值(缓存计算结果)。它们都自动追踪依赖——你在里面读了哪些 signal,哪些变了就重跑。

  3. 编译时 JSX 优化:Solid 的编译器把 <h1>{name()}</h1> 拆成”一次创建 DOM + 一个订阅函数绑到那个文本节点”。没有 virtual DOM,没有 diff,只有最小订阅单位的精准更新。

实践案例

案例 1:最小计数器

import { createSignal } from 'solid-js'
function Counter() {
const [count, setCount] = createSignal(0)
return <button onClick={() => setCount(count() + 1)}>{count()}</button>
}

逐行解释

  • createSignal(0) 返回 [getter, setter]——count 是函数,不是变量
  • JSX 里 {count()} 调用 getter——这一调用就把”这个文本节点”订阅到 count
  • 点按钮 → setCount(...) → Solid 通知所有订阅者 → 只更新那个文本节点
  • Counter 函数本身只跑了一次,整个生命周期都不会再跑

案例 2:用 createEffect 追副作用

import { createSignal, createEffect } from 'solid-js'
function Logger() {
const [name, setName] = createSignal('Jason')
createEffect(() => {
console.log('name 变成了', name())
})
return <input value={name()} onInput={e => setName(e.target.value)} />
}

createEffect 自动追踪 name()——每次输入框变化就打日志。没有依赖数组——你读了什么它就追什么,比 React 的 useEffect([deps]) 心智简单很多。

案例 3:嵌套 createMemo 派生

import { createSignal, createMemo } from 'solid-js'
function Cart() {
const [items, setItems] = createSignal([{ price: 10 }, { price: 20 }])
const total = createMemo(() => items().reduce((s, i) => s + i.price, 0))
const taxed = createMemo(() => total() * 1.1)
return <div>含税总价:{taxed()}</div>
}

total 依赖 itemstaxed 依赖 totalitems 变 → total 重算 → taxed 重算 → 只更新那个 <div> 的文本。整条链路是惰性 + 缓存的——没人读 taxed() 它就不算。

踩过的坑

  1. 组件函数只跑一次,不能用 React useState 思维:在组件函数体里写 if (count() > 5) ... 永远只走第一次的分支,因为函数本身不会重跑。条件渲染要用 JSX 内联表达式或 <Show when={...}>

  2. 必须 count() 调用才订阅,写 count 是函数引用<h1>{count}</h1> 把 getter 函数本身渲染成字符串,不会订阅。新人最常见的报错来源。

  3. JSX 块外面解构 props 会丢响应式function Foo(props) { const { x } = props; return <p>{x}</p> }——x 一旦解构出来就是普通值,丢了 props 的 getter 代理,再变也不会更新。要写 props.x 或用官方的 splitProps / mergeProps

  4. 与 React 库不兼容,生态小很多:React 表单、动画、状态管理库(react-hook-formframer-motionzustand)都不能直接用——Solid 有自己的对应物(solid-forms@motionone/solid),但数量级少一两个。选 Solid 等于选一个更小的生态。

适用 vs 不适用场景

适用

  • 性能敏感的复杂界面(dashboard / 实时数据 / 编辑器)—— 细粒度更新优势明显
  • 嵌入式 / 小体积场景 —— Solid runtime 比 React 小一个数量级(约 7KB gzip)
  • 想体验”现代响应式”心智的学习项目 —— 比 React 更直接、更接近 Vue 3 的 Composition API
  • SSR + 路由的中型应用 —— SolidStart 提供完整的 vite 集成方案

不适用

  • 团队已经全员熟 React —— 切换成本高,且 Solid 招聘市场远小于 React
  • 重度依赖现成 React 库(shadcn-ui / react-spring 等)—— 在 Solid 里要找替代或自己写
  • 静态营销页 —— 用 Astro / 11ty 更合适,Solid 的反应式优势用不上
  • 团队还不熟”信号”心智 —— 解构丢响应式这种坑会反复踩,比 React 更需要训练

历史小故事(可跳过)

  • 2018 年:Ryan Carniato 在工作中维护一个老 Knockout.js 项目,受其细粒度反应式启发,业余时间开始造 Solid 原型
  • 2019 年 1 月:Solid 0.9 发布,第一次进入 JS Framework Benchmark 视野,性能榜单前三
  • 2021 年 6 月:Solid 1.0 正式发布,API 稳定,社区开始增长
  • 2022 年 8 月:SolidStart 公布,对标 Next.js / Remix,把 Solid 从”组件库”变成”全栈框架”
  • 2024 年:React Compiler 公开,被普遍认为吸收了 Solid / Svelte 5 这一派”编译期细粒度追踪”的思路

学到什么

  1. 响应式不一定要靠”重渲染”:React 的”数据变 → 函数重跑 → diff DOM”只是一种实现,Solid 证明了”数据变 → 直接通知订阅者 → 改 DOM”是可行且更快的另一条路
  2. 信号是过去 5 年最重要的前端概念:Vue 3 的 ref、Svelte 5 的 $state、Angular 的 signal()、Preact 的 signal——本质都是 Solid 这套思路的近亲
  3. JSX 不等于 React:JSX 只是语法,背后的语义(每次重跑 vs 只跑一次)由编译器和 runtime 共同决定
  4. 小生态的代价是真实的:性能优势不能直接翻译成生产力,团队选型要权衡”框架性能 + 生态成熟度 + 招聘”三件事

延伸阅读

关联

  • react —— 同样的 JSX 语法,相反的执行模型;理解 Solid 必先对比 React
  • vue —— Vue 3 Composition API 与 Solid 信号心智高度相似
  • vite —— SolidStart 默认构建工具,Solid 编译插件就是 Vite plugin
  • mobx —— React 生态里的”信号派”实现,思路和 Solid 一致但寄生在重渲染框架上
  • zustand —— React 状态管理,Solid 因有 signal 不需要这类库
  • tanstack-query —— Solid 也能用(有 @tanstack/solid-query 端口),是少数跨框架的好库

反向链接

  • astro —— Astro — 内容站点优先的 Web 框架
  • mobx —— MobX — 让 state 像电子表格一样自动重算
  • qwik —— Qwik — Resumable UI 框架
  • react —— React UI 组件库
  • self-adjusting —— Self-Adjusting Computation — 输入小幅变化时只重算受影响的那部分
  • svelte —— Svelte — 编译时 UI 框架
  • tanstack-query —— TanStack Query — 数据获取与缓存库
  • vite —— Vite — 浏览器自己加载源码的构建工具
  • vue —— Vue.js — 渐进式 UI 框架
  • zustand —— Zustand — 极简 React 状态管理