我们用 SolidJS 和 React 构建了相同的应用:Signals vs Hooks
上个季度,我们有一个客户项目,完美契合我们一直想尝试的事情:用 SolidJS 和 React 构建相同的应用,然后比较所有方面。不是待办事项应用。不是计数器。而是一个真实的生产仪表板,包括身份验证、实时数据更新、复杂表单和呈现数千个数据点的图表模块。
结果让我们惊讶。不是因为某个框架「获胜」——而是因为权衡远比 Twitter 上的讨论要细致得多。有些事情如我们预期。有些事情完全出乎意料。这就是我们学到的真正内容。
目录
- 我们构建的应用
- Signals vs Hooks:心智模型的转变
- Bundle 大小对比
- 渲染性能基准
- 开发者体验和生态系统
- 实际重要的生产权衡
- 何时选择 SolidJS 而非 React
- 代码对比:真实模式
- 常见问题

我们构建的应用
该应用是一个电子商务客户的实时分析仪表板。包括以下内容:
- 身份验证流程,使用 JWT 令牌和刷新逻辑
- 仪表板,包含 6 个 widget 面板,每个面板从不同的 API 端点拉取数据
- 实时订单源,使用 WebSocket 连接
- 交互式图表,呈现 5,000+ 个数据点(使用图表库)
- 复杂过滤表单,包含依赖下拉菜单和日期范围选择器
- 管理设置面板,包含嵌套状态管理
- 响应式布局,带有侧边栏导航
两个版本都连接到同一个后端 API。两个版本都使用 TypeScript。两个版本都使用 Vite 作为构建工具。我们尽可能保持第三方依赖相似——相同的图表库 (Chart.js)、相同的 HTTP 客户端 (ky)、相同的 WebSocket 包装器。
React 版本使用 React 19 配合 hooks。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');
// 除非我们记忆化,否则每次渲染都会重新计算
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 的模型从根本上不同。组件函数运行一次。之后,只有那些读取 signal 的特定表达式在该 signal 改变时重新执行。没有虚拟 DOM diff。没有调和。响应式图谱精确追踪哪些 DOM 节点依赖于哪些 signals。
// 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 中很棘手。 你不能在 Solid 组件的顶部执行
if (!data()) return <Loading />,就像在 React 中那样。组件函数只运行一次,所以该条件永远不会重新评估。你需要<Show when={data()}>代替。没有陈旧闭包 bug。 这是大赢家。在我们的 React 版本中,第一周有三个单独的陈旧闭包 bug,与 WebSocket 处理器捕获旧状态有关。Solid 版本有零个,因为 signals 在访问时总是被读取,而不是在闭包中被捕获。
Bundle 大小对比
我们使用完全相同的 Vite 配置、gzip 压缩和代码分割策略来测量两个生产构建。
| 指标 | React 19 | SolidJS 1.9 | 差异 |
|---|---|---|---|
| 框架运行时 | 44.2 KB (gzip) | 7.1 KB (gzip) | -84% |
| 总初始 bundle | 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 比 React tree-shake 得更好。未使用的 Solid 原始类型完全被消除。对于 React,调和器和 fiber 架构无论你是否使用所有功能都会被发布。我们通过在两个框架中构建最小页面来确认这一点——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 树进行 diff。当过滤 signal 改变时,只有读取该 signal 的表达式更新。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 正在批处理更新(这很好),但在每个批次上仍在 diffing 比必要更多的 DOM。
我们应该注意:React 19 的 useTransition 和自动批处理使这比 React 18 会有的情况好得多。对于大多数真实应用,你不会每秒推送 100 个更新。在 10 次更新/秒,两个框架都很流畅。
包含 40 个字段的复杂表单
| 指标 | React 19 | SolidJS 1.9 |
|---|---|---|
| 按键输入延迟 | 2-4ms | <1ms |
| 表单提交渲染 | 28ms | 12ms |
| 每个按键的组件重新渲染 | 3-8 | 1 |
在 React 中,在一个字段中输入会导致父组件重新渲染,除非我们小心地记忆化所有内容。在 Solid 中,在字段中输入只会更新该字段的 DOM 文本节点。其他任何东西都不会重新执行。
开发者体验和生态系统
性能不是一切。你必须实际构建这个东西、调试它、为它招聘人员并维护它。
生态系统规模(截至 2025 年初)
| 因素 | React | SolidJS |
|---|---|---|
| npm 周下载 | ~28M | ~85K |
| GitHub stars | 233K+ | 33K+ |
| Stack Overflow 问题 | 470K+ | ~2K |
| UI 组件库 | 50+ 成熟选项 | 5-8 选项 |
| 招聘职位(LinkedIn 美国) | ~45,000 | ~200 |
| 元框架 | Next.js(成熟) | SolidStart(beta) |
这是房间里的大象。React 的生态系统大几个数量级。当我们需要一个 Solid 的日期范围选择器时,我们要么必须移植一个 React 的,要么自己编写。在 React 中,我们有 15 个选项可供选择。
我们在 Solid 中使用 Kobalte 用于可访问的 UI 原始类型,它真的很好。但就组件覆盖而言,它不如 Radix UI。
调试
React DevTools 很成熟、维护良好,是每个前端开发人员都知道的东西。Solid 有自己的 DevTools 扩展,相当不错——你可以检查 signal 图,这对追踪响应式 bug 实际上比 React 的组件树视图更有用。但它不够精致。
TypeScript 支持
两者都很优秀。Solid 的 TypeScript 支持对 JSX 来说实际上稍好一些,因为编译器在构建时处理更多。我们在 Solid 代码库中进行的类型体操较少。
实际重要的生产权衡
构建两个版本并将 Solid 版本部署到暂存(客户最终为生产选择了 React——更多内容见下文)后,以下是实际重要的权衡:
招聘
我们的客户有一个包含 8 位前端开发人员的团队。都知道 React。都没有使用过 Solid。培训时间估计:达到熟练水平需要 2-3 周,掌握需要 2-3 个月。那是真实成本。这是大多数生产决策中的单一最大因素,也是为什么我们通常对需要频繁招聘的团队推荐 React 或像 Next.js 这样的框架。
第三方库兼容性
我们必须为三个有 React 特定集成的库编写自定义包装器。这增加了大约 20 小时的开发时间。在 React 项目中,这些时间不存在。
服务器端渲染
SolidStart 很有前景,但在我们的项目期间仍然处于 beta 阶段。Next.js 经过实战测试。对于需要生产级 SSR 和所有功能的项目,我们仍然根据内容模型选择 Next.js 开发或 Astro。
性能最重要的地方
以下是老实话:对于这个特定的仪表板,两个框架在生产中表现都足够好。Solid 版本可测量更快,但 React 版本从未缓慢。用户在大多数交互中不会注意到差异。
例外是高更新频率的实时源。如果你的应用有这样的用例,Solid 的性能优势是有意义的,而不仅仅是基准吹牛。
何时选择 SolidJS 而非 React
在这个实验之后,这是我们对决策的诚实框架:
选择 SolidJS 当:
- 你的应用有高频响应式更新(仪表板、交易平台、实时协作)
- Bundle 大小很关键(嵌入式 widget、微前端、移动优先)
- 你的团队很小,愿意投资学习新范式
- 你想要细粒度响应式而不与框架对抗
- 你在构建新的东西,不需要庞大的组件库生态系统
选择 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> 组件存在是因为当组件函数只运行一次时,三元组不以相同方式工作。它需要适应,但回调模式给你一个窄化的、非 null 的引用,这对 TypeScript 很好。
列表渲染
React:
{orders.map(order => (
<OrderRow key={order.id} order={order} />
))}
SolidJS:
<For each={orders()}>
{(order) => <OrderRow order={order} />}
</For>
Solid 的 <For> 不需要 keys,因为它通过引用追踪数组项。当项在数组中移动时,Solid 移动实际 DOM 节点。React 会卸载和重新挂载。这是为什么 Solid 的列表性能这么好。
常见问题
SolidJS 在 2025 年是生产就绪的吗? Yes. Solid 1.x 已经稳定超过两年。Cloudflare 和三星等公司已经在生产中使用它。核心库成熟且经过充分测试。它周围的生态系统(SolidStart、组件库)比 React 的小但在增长。问题不是 Solid 是否就绪——而是你的团队和项目要求是否与其生态系统规模一致。
我可以增量从 React 迁移到 SolidJS 吗? 不容易。尽管 JSX 语法相似,但运行时模型从根本上不同。你不能在 React 应用内嵌入 Solid 组件(反之亦然),除非通过 iframe 或 web 组件边界。迁移本质上是重写。如果你在考虑,开始使用新的隔离功能或微前端,而不是尝试转换现有 React 代码。
SolidJS 支持服务器端渲染吗? Yes. SolidStart 提供 SSR、流媒体和服务器函数。它是函数式且不断改进,但截至 2025 年初,它仍不如 Next.js 或 Remix 成熟。对于 SSR 密集型项目,我们仍然会建议根据你的内容需求评估 Next.js 或 Astro。
React 19 的编译器与 Solid 的方法如何比较? React 19 的编译器(原来的 React Forget)自动记忆化组件以减少不必要的重新渲染。这是一个重大改进。但它仍在 React 重新运行组件函数和 diffing 虚拟 DOM 的模型内工作。Solid 完全跳过这两个步骤。编译器使 React 更快,但它不改变基础架构。在我们的基准中,带编译器的 React 19 比 React 18 快约 30%,但在更新繁重的场景中仍慢于 Solid。
Preact Signals 作为中间地带如何?
Preact Signals(和 @preact/signals-react 适配器)为 React 生态系统带来了类似 signal 的响应式。这是一个有趣的方法,但它是在与 React 的核心模型对抗而不是与之配合。我们简要测试过它,发现 Suspense 和并发功能的边界情况。如果你想要 signals,Solid 给你完整的体验而没有阻抗失配。
SolidJS 比 React 更难学吗?
如果你已经知道 React,Solid 的 API 看起来会很熟悉——类似 JSX、类似模式。困难部分是遗忘 React 的心智模型。你会到达 useEffect 模式不存在的地方。你会解构 props 并破坏响应式。你会尝试早期返回并想知道为什么它们不工作。为有经验的 React 开发人员在 Solid 中变得有效预算大约 1-2 周,再花一两个月停止写 React 风格的 Solid。
哪个框架的 TypeScript 支持更好?
两者都有优秀的 TypeScript 支持。Solid 有轻微优势,因为其编译器可以在某些模式中提供更窄的类型(如 <Show> 中的回调),你不需要复杂 React hooks 有时需要的相同数量的通用类型参数。但老实说,两者都很好。这不应该是决定因素。
我应该在下一个项目中使用 SolidJS 吗? 这取决于你的约束。如果你是一个小团队构建对性能敏感的东西,愿意投资学习曲线和处理更小的生态系统,Solid 是一个真正优秀的选择。如果你需要招聘 React 开发人员、使用已建立的组件库或需要为生产战斗测试的元框架,React 是更安全的赌注。没有通用答案——只有适合你特定情况的答案。如果你想讨论你项目的决策,联系我们,我们可以帮你评估权衡。