我们用 SolidJS 和 React 构建了同一个应用:Signals vs Hooks
上个季度,我们有一个客户项目,让我们有了完美的借口去尝试一直想做的事情:用 SolidJS 和 React 构建同一个应用,然后进行全面对比。不是 todo 应用。不是计数器。而是一个真实的生产仪表盘,具有身份验证、实时数据更新、复杂表单,以及呈现数千个数据点的图表模块。
结果让我们感到惊讶。不是因为某个框架"赢了" -- 而是因为权衡之处远比 Twitter 上的讨论要微妙得多。有些东西是我们预料到的。有些东西完全出乎我们的意料。这就是我们真正学到的。
目录
- 我们构建的应用
- Signals vs Hooks:思维模式转变
- 包大小对比
- 渲染性能基准
- 开发者体验和生态系统
- 生产中真正重要的权衡
- 何时选择 SolidJS 而不是 React
- 代码对比:真实模式
- 常见问题

我们构建的应用
该应用是一个用于电商客户的实时分析仪表盘。以下是它包含的内容:
- 身份验证流程,包含 JWT 令牌和刷新逻辑
- 包含 6 个小部件面板的仪表盘,每个面板从不同的 API 端点拉取数据
- 实时订单提要,使用 WebSocket 连接
- 交互式图表,呈现 5,000+ 个数据点(使用图表库)
- 复杂过滤表单,具有依赖下拉列表和日期范围选择器
- 管理员设置面板,具有嵌套的状态管理
- 响应式布局,带有侧边栏导航
两个版本都连接到相同的后端 API。两个都使用 TypeScript。两个都使用 Vite 作为构建工具。我们尽可能保持第三方依赖相似 -- 相同的图表库(Chart.js)、相同的 HTTP 客户端(ky)、相同的 WebSocket 包装器。
React 版本使用带有 hooks 的 React 19。SolidJS 版本使用 Solid 1.9。我们没有使用任何元框架(没有 Next.js,没有 SolidStart),因为我们想在路由和 SSR 混淆对比的情况下隔离框架差异。
Signals vs Hooks:思维模式转变
这是最重要的部分。老实说,我们的团队花了约一周的时间才能停止在 SolidJS 中写 React 风格的代码。
React Hooks 如何工作
你已经知道这个,但为了进行对比,值得明确陈述。React 的模型是:当状态改变时,组件函数重新执行。整个函数。每个 useState、每个 useMemo、每个 useCallback -- 它们都是围绕整个函数重新运行这一事实进行的机制。
// React:当状态改变时,整个函数重新运行
function OrderFeed() {
const [orders, setOrders] = useState([]);
const [filter, setFilter] = useState('all');
// 除非我们进行 memoize,否则每次渲染都会重新计算
const filteredOrders = useMemo(() =>
orders.filter(o => filter === 'all' || o.status === filter),
[orders, filter]
);
// 此 ref 在概念上每次渲染都会被重新创建
const handleNewOrder = useCallback((order) => {
setOrders(prev => [order, ...prev]);
}, []);
useEffect(() => {
const ws = connectWebSocket(handleNewOrder);
return () => ws.close();
}, [handleNewOrder]);
return <OrderList orders={filteredOrders} />;
}
Solid Signals 如何工作
Solid 的模型从根本上是不同的。组件函数只运行一次。之后,只有读取信号的特定表达式在该信号改变时重新执行。没有虚拟 DOM 差异。没有协调。响应图准确追踪哪些 DOM 节点依赖于哪些信号。
// Solid:此函数运行一次
function OrderFeed() {
const [orders, setOrders] = createSignal([]);
const [filter, setFilter] = createSignal('all');
// 这会自动被追踪 -- 不需要依赖数组
const filteredOrders = createMemo(() =>
orders().filter(o => filter() === 'all' || o.status === filter())
);
// 不需要 useCallback -- 此闭包是稳定的
const handleNewOrder = (order) => {
setOrders(prev => [order, ...prev]);
};
// onMount 而不是带空依赖的 useEffect
onMount(() => {
const ws = connectWebSocket(handleNewOrder);
onCleanup(() => ws.close());
});
return <OrderList orders={filteredOrders()} />;
}
这在实践中意味着什么
Solid 版本没有依赖数组。没有 useCallback。没有用于派生状态的 useMemo(尽管 createMemo 存在于昂贵计算中 -- 区别在于它是性能优化的可选项,而不是正确性的必需项)。
这是开发过程中真正困扰我们的地方:
在 Solid 中解构 props 会破坏响应性。 我们的一位初级开发人员在 Solid 组件中解构了 props,我们花了 45 分钟来调试为什么更新没有传播。在 React 中,解构是可以的,因为整个函数重新运行。在 Solid 中,你需要在 JSX 或追踪的作用域内访问
props.value。Solid 中的早期返回很棘手。 你不能像在 React 中那样在 Solid 组件顶部执行
if (!data()) return <Loading />。组件函数只运行一次,所以该条件会永远不会重新评估。你需要使用<Show when={data()}>代替。没有陈旧闭包 bug。 这是大赢家。在我们的 React 版本中,第一周我们有三个独立的陈旧闭包 bug,与 WebSocket 处理程序捕获旧状态有关。Solid 版本有零个,因为信号总是在访问时读取,而不是在闭包中捕获。
包大小对比
我们使用完全相同的 Vite 配置、gzip 压缩和代码分割策略测量了两个生产构建。
| 指标 | React 19 | SolidJS 1.9 | 差异 |
|---|---|---|---|
| 框架运行时 | 44.2 KB (gzip) | 7.1 KB (gzip) | -84% |
| 总初始包 | 127.8 KB | 89.3 KB | -30% |
| 总应用(所有块) | 312.4 KB | 274.1 KB | -12% |
| 首个块(关键路径) | 68.4 KB | 41.2 KB | -40% |
| 块数量 | 14 | 14 | 相同 |
Solid 的运行时明显更小。这不是新闻 -- Ryan Carniato 已经广泛讨论过这个问题。但让我们感到惊讶的是,一旦你添加真实的应用代码、第三方库和资产,总应用大小的差异显著缩小。
框架运行时对初始加载最重要。在 7.1 KB gzip 的情况下,Solid 基本上消失在噪声中。React 的 44 KB 是可以注意到的,特别是在移动连接上。
Tree Shaking
Solid 的 tree shaking 比 React 更好。未使用的 Solid 原始函数会被完全消除。使用 React 时,协调器和纤维架构无论你是否使用它们的所有功能都会被发送。我们通过在两者中构建最小页面来确认这一点 -- Solid 的下限更低。

渲染性能基准
我们使用 Chrome DevTools 性能配置文件、Lighthouse 和自定义计时工具运行了结构化基准。所有测试都在 M2 MacBook Pro 上进行,CPU 限制设置为 4 倍减速以模拟中端设备。
图表渲染(5,000 个数据点)
| 指标 | React 19 | SolidJS 1.9 |
|---|---|---|
| 初始渲染 | 142ms | 89ms |
| 在过滤器更改时重新渲染 | 67ms | 23ms |
| 渲染期间内存 | 18.4 MB | 11.2 MB |
| 创建的 DOM 节点 | 5,847 | 5,812 |
Solid 在重新渲染时始终更快,因为它不差异虚拟 DOM 树。当过滤器信号改变时,只有读取该信号的表达式会更新。React 19 的编译器改进有所帮助 -- 相同的测试在 React 18 上是 95ms 用于重新渲染 -- 但 Solid 仍然具有明显优势。
实时订单提要(每秒 100 次更新)
这是事情变得非常有趣的地方。我们将每秒 100 条 WebSocket 消息推入订单提要。
| 指标 | React 19 | SolidJS 1.9 |
|---|---|---|
| 帧丢失(每 10 秒) | 12 | 2 |
| 平均绘制时间 | 8.3ms | 3.1ms |
| 最大绘制时间 | 34ms | 11ms |
| CPU 使用率(平均) | 24% | 9% |
Solid 在这个基准中绝对压倒性赢了。高频更新是细粒度响应性发挥最大优势的地方。React 正在批处理更新(这很好),但在每个批次中仍在差异比必要的更多 DOM。
我们应该注意:React 19 的 useTransition 和自动批处理使这比 React 18 好得多。而且对于大多数真实世界的应用,你不会推送每秒 100 次更新。在每秒 10 次更新时,两个框架都很流畅。
包含 40 个字段的复杂表单
| 指标 | React 19 | SolidJS 1.9 |
|---|---|---|
| 按键输入延迟 | 2-4ms | <1ms |
| 表单提交渲染 | 28ms | 12ms |
| 每次按键的组件重新渲染 | 3-8 | 1 |
在 React 中,在一个字段中输入会导致父组件重新渲染,除非我们仔细 memoize 所有内容。在 Solid 中,在字段中输入字面上只更新该字段的 DOM 文本节点。没有其他东西重新执行。
开发者体验和生态系统
性能不是一切。你必须实际构建这个东西,调试它,为它招聘,并维护它。
生态系统规模(截至 2026 年初)
| 因素 | React | SolidJS |
|---|---|---|
| npm 周下载量 | ~28M | ~85K |
| GitHub stars | 233K+ | 33K+ |
| Stack Overflow 问题 | 470K+ | ~2K |
| UI 组件库 | 50+ 成熟选项 | 5-8 个选项 |
| 职位发布(LinkedIn 美国) | ~45,000 | ~200 |
| 元框架 | Next.js(成熟) | SolidStart(测试版) |
这是房间里的大象。React 的生态系统要大得多。当我们需要为 Solid 提供日期范围选择器时,我们必须移植 React 的或自己编写。在 React 中,我们有 15 个选项可以选择。
我们为 Solid 使用了 Kobalte 作为可访问的 UI 原始函数,它确实不错。但在组件覆盖范围方面,它还是无法与 Radix UI 相比。
调试
React DevTools 成熟、维护良好,是每个前端开发者都知道的。Solid 有自己的 DevTools 扩展,它还不错 -- 你可以检查信号图,这实际上对于追踪响应性 bug 比 React 的组件树视图更有用。但它不够精致。
TypeScript 支持
两者都很优秀。Solid 的 TypeScript 支持对 JSX 实际上稍好一些,因为编译器在构建时处理更多。我们在 Solid 代码库中进行了更少的类型体操。
生产中真正重要的权衡
在构建两个版本并将 Solid 版本部署到 staging 后(客户最终在生产中选择了 React -- 下面会详细说明),以下是真正重要的权衡:
招聘
我们的客户有一个由 8 名前端开发者组成的团队。所有人都了解 React。没有人使用过 Solid。培训时间估计:熟练度 2-3 周,掌握 2-3 个月。这是真实成本。这是大多数生产决策中的单一最大因素,也是为什么我们通常建议 React 或 Next.js 等框架用于需要频繁招聘的团队。
第三方库兼容性
我们必须为三个具有 React 特定集成的库编写自定义包装器。这增加了大约 20 小时的开发时间。在 React 项目中,这些小时不存在。
服务器端渲染
SolidStart 很有前景,但在我们的项目期间仍处于测试阶段。Next.js 经过战斗测试。对于需要生产级 SSR 和所有功能的项目,我们仍在根据内容模型选择 Next.js 开发或 Astro。
性能在它重要的地方
老实说,对于这个特定的仪表盘,两个框架的生产性能都足够好。Solid 版本速度明显更快,但 React 版本从不慢。用户在大多数交互中不会注意到差异。
例外是高更新频率的实时提要。如果你的应用有这样的用例,Solid 的性能优势是有意义的,而不仅仅是基准吹嘘。
何时选择 SolidJS 而不是 React
在这个实验之后,这是我们对决定的诚实框架:
选择 SolidJS 当:
- 你的应用具有高频响应式更新(仪表盘、交易平台、实时协作)
- 包大小很关键(嵌入式小部件、微前端、移动优先)
- 你的团队很小且愿意投资学习新的范式
- 你想要细粒度响应性而不与框架对抗
- 你正在构建新的东西并且不需要庞大的组件库生态系统
选择 React 当:
- 你的团队已经了解 React(这一点通常是决定性的)
- 你需要成熟的元框架(Next.js、Remix)
- 第三方库可用性很重要
- 你正在招聘并需要大量的人才库
- 你的应用的性能要求是典型的(不是极端的)
对于我们的 headless CMS 开发项目,React 仍然占主导地位,因为 CMS 集成(Sanity、Contentful、Storyblok)具有一流的 React SDK。Solid 支持存在但通常由社区维护。
代码对比:真实模式
让我们看看项目中并排的一些真实模式。
带加载状态的数据获取
React:
function Dashboard() {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchDashboardData()
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, []);
if (loading) return <Skeleton />;
if (error) return <ErrorBanner error={error} />;
return <DashboardGrid data={data} />;
}
SolidJS:
function Dashboard() {
const [data] = createResource(fetchDashboardData);
return (
<Suspense fallback={<Skeleton />}>
<ErrorBoundary fallback={(err) => <ErrorBanner error={err} />}>
<DashboardGrid data={data()} />
</ErrorBoundary>
</Suspense>
);
}
Solid 的 createResource 和 Suspense 真的很优雅。React 也有 Suspense(而且它在 React 19 中变得更好),但 Solid 的版本感觉更自然工作。更少样板代码,更少状态变量要管理。
条件渲染
React:
{user ? (
<Profile user={user} />
) : (
<LoginPrompt />
)}
SolidJS:
<Show when={user()} fallback={<LoginPrompt />}>
{(u) => <Profile user={u()} />}
</Show>
Solid 的 <Show> 组件存在是因为当组件函数只运行一次时,三元运算符不会以相同的方式工作。习惯之后,回调模式给你一个缩小的、非空的引用,这对 TypeScript 很好。
列表渲染
React:
{orders.map(order => (
<OrderRow key={order.id} order={order} />
))}
SolidJS:
<For each={orders()}>
{(order) => <OrderRow order={order} />}
</For>
Solid 的 <For> 不需要键,因为它按引用追踪数组项。当项在数组中移动时,Solid 移动实际的 DOM 节点。React 会卸载并重新挂载。这就是为什么 Solid 的列表性能这么好。
常见问题
SolidJS 在 2026 年生产就绪吗? yes。Solid 1.x 已经稳定了两年多。Cloudflare 和三星等公司在生产中使用过它。核心库是成熟和经过充分测试的。它周围的生态系统(SolidStart、组件库)比 React 的小,但增长势头强劲。问题不是 Solid 是否就绪 -- 而是你的团队和项目要求是否与其生态系统规模相符。
我可以从 React 增量迁移到 SolidJS 吗? 不太容易。尽管 JSX 语法相似,运行时模型从根本上是不同的。你不能在 React 应用内嵌入 Solid 组件(反之亦然),除非有 iframe 或 web 组件边界。迁移基本上会是一次重写。如果你正在考虑,从一个新的隔离功能或微前端开始,而不是尝试转换现有的 React 代码。
SolidJS 支持服务器端渲染吗? 支持。SolidStart 提供 SSR、流和服务器函数。它是可用的并且改进迅速,但截至 2026 年初,它仍然不如 Next.js 或 Remix 成熟。对于 SSR 繁重的项目,我们仍然会建议评估 Next.js 或 Astro,取决于你的内容需求。
React 19 的编译器与 Solid 的方法相比如何? React 19 的编译器(原名 React Forget)自动 memoize 组件以减少不必要的重新渲染。这是一个重大改进。但它仍在 React 的模型内工作,即重新运行组件函数和差异虚拟 DOM。Solid 完全跳过这两个步骤。编译器使 React 更快,但它不改变基本架构。在我们的基准中,带编译器的 React 19 比 React 18 快约 30%,但在更新繁重的场景中仍然比 Solid 慢。
Preact Signals 作为中间地带怎么样?
Preact Signals(以及 @preact/signals-react 适配器)为 React 生态系统带来类似信号的响应性。这是一个有趣的方法,但它与 React 的核心模型对抗,而不是与之配合工作。我们简要测试了它,找到了 Suspense 和并发功能的边界情况。如果你想要信号,Solid 给你完整的体验而不需要阻抗失配。
SolidJS 比 React 更难学吗?
如果你已经了解 React,Solid 的 API 会看起来很熟悉 -- 相似的 JSX、相似的模式。困难部分是遗忘 React 的心智模型。你会去找 useEffect 模式,但它们不存在。你会解构 props 并破坏响应性。你会尝试早期返回并想知道为什么它们不工作。对于有经验的 React 开发者,预计约 1-2 周才能在 Solid 中变得高效,还要再花 1-2 个月才能停止写 React 风格的 Solid。
哪个框架的 TypeScript 支持更好?
两者都有优秀的 TypeScript 支持。Solid 有一点优势,因为它的编译器可以在某些模式中提供更窄的类型(比如 <Show> 中的回调),并且你不需要复杂 React hooks 有时需要的相同体量的通用类型参数。但老实说,两者都很好。这不应该是决定性因素。
我应该为我的下一个项目使用 SolidJS 吗? 这取决于你的约束。如果你是一个小团队,构建性能敏感的东西,并且愿意投资学习曲线和解决更小的生态系统,Solid 是一个真正出色的选择。如果你需要招聘 React 开发者、使用已建立的组件库或需要用于生产的战斗测试元框架,React 是更安全的赌注。没有普遍答案 -- 只有针对你具体情况的正确答案。如果你想讨论你的项目决定,与我们联系,我们可以帮助你评估权衡。