SolidJS 一直以"无虚拟 DOM、细粒度响应式"的标签在框架圈里独树一帜。2.0 Beta 的发布不是小修小补——它把异步从"需要绕路处理的边缘场景"抬到了框架核心位置,同时重新设计了 Suspense、引入了 mutation 原语、调整了状态处理模型,并附带一批不可忽视的 breaking changes。如果你已经在用 SolidJS,这次升级需要认真规划;如果你在选型阶段,2.0 的异步体验值得重点评估。
异步不再需要"包装层"
1.x 时代,在 SolidJS 里处理异步数据通常要借助 createResource——它本质上是一个专门把 Promise 包装成响应式信号的桥接层。开发者必须显式调用 createResource(fetchFn),再通过 .value、.loading、.error 去读取结果。这套机制能用,但心智模型和原生 JavaScript 的 Promise 链有距离。
2.0 的核心变化:Promise 本身就是响应式数据源。你可以在组件里直接使用 Promise,框架会追踪它的 resolve/reject 状态并触发细粒度更新,不再需要中间包装。这意味着:
async function的返回值可以直接在模板中消费- 数据获取的逻辑可以写得更接近原生 JavaScript 习惯
- Suspense 的触发条件从"resource 正在加载"变为"Promise 尚未 resolve"
下面是一个对比示例,展示 1.x 和 2.0 在获取远程数据时的写法差异:
// SolidJS 1.x — 必须用 createResource 包装
import { createResource } from "solid-js";
function UserProfile({ userId }: { userId: string }) {
const [user] = createResource(() => userId, fetchUser);
return (
<div>
{user.loading && <p>加载中...</p>}
{user() && <h2>{user().name}</h2>}
</div>
);
}
// SolidJS 2.0 — Promise 直接作为一等公民(基于 Beta 公开信息 + 合理推断的实践写法)
async function fetchUser(id: string) {
const res = await fetch(`/api/users/${id}`);
return res.json();
}
function UserProfile({ userId }: { userId: string }) {
// Promise 可以直接在响应式上下文中使用
const user = fetchUser(userId);
return (
<Suspense fallback={<p>加载中...</p>}>
<h2>{user.name}</h2>
</Suspense>
);
}
注意:2.0 Beta 的具体 API 语法可能还在调整中,上面的 2.0 写法是基于"Promise 一等公民"设计方向的合理推断。正式文档发布前,建议以仓库和 changelog 为准。
重构后的 Suspense:确定性边界
1.x 的 Suspense 有一个让开发者头疼的问题:批处理的不确定性。当多个 resource 同时触发加载时,Suspense 的 fallback 显示时机和嵌套行为有时难以预测,尤其在并发请求和嵌套组件场景下。
2.0 对 Suspense 的重新设计围绕"确定性批处理"展开:
- 确定性批处理(Deterministic Batching):多个异步操作在同一渲染周期内触发时,框架会明确界定哪些更新属于同一批次,Suspense 的 fallback 不会在中间状态闪烁
- 嵌套 Suspense 边界更清晰:外层 Suspense 不会因为内层某个 Promise 还没 resolve 就反复切换 fallback,每个边界只关心自己直接管辖的异步操作
- 与细粒度响应式的兼容性:即使某个异步值更新了,只有真正依赖这个值的 DOM 节点会重新渲染,不会像虚拟 DOM 框架那样整棵子树 diff
一个典型的嵌套 Suspense 场景可以这样写:
import { Suspense } from "solid-js";
function App() {
return (
<Suspense fallback={<ShellLayout />}>
<Nav />
<Suspense fallback={<ArticleSkeleton />}>
<Article id="solid-2-beta" />
</Suspense>
<Sidebar />
</Suspense>
);
}
async function Article({ id }: { id: string }) {
const data = fetchArticle(id); // Promise 一等公民
const comments = fetchComments(id);
return (
<Suspense fallback={<CommentsSkeleton />}>
<h1>{data.title}</h1>
<p>{data.body}</p>
<CommentList comments={comments} />
</Suspense>
);
}
外层 ShellLayout 在整体数据就绪前显示;内层 ArticleSkeleton 只在文章数据加载时出现;CommentsSkeleton 只在评论加载时出现。三层边界各自独立,不会互相干扰。
Mutation 原语:写操作有了专属抽象
1.x 的响应式体系偏向"读取驱动"——createSignal、createStore 都是声明数据源,然后通过 setter 触发更新。对于写操作(提交表单、POST 请求、乐观更新),开发者通常要自己组合 signal + effect + error handling。
2.0 引入了 mutation 原语,专门处理"触发 → 乐观更新 → 确认/回滚"这类写操作模式。这和 React Query 的 useMutation 思路相近,但和 SolidJS 的细粒度响应式深度整合。
一个表单提交的 mutation 可以这样实践:
import { createMutation } from "solid-js"; // 2.0 新增原语(API 名称以正式文档为准)
function SaveButton({ articleId }: { articleId: string }) {
const saveMutation = createMutation({
mutationFn: (draft: Draft) => fetch(`/api/articles/${articleId}`, {
method: "PUT",
body: JSON.stringify(draft),
}).then(r => r.json()),
// 乐观更新:提交前先在本地写入新值
onMutate: async (draft) => {
// 在 store 中暂存草稿,用户立刻看到变化
updateLocalArticle(articleId, draft);
},
// 成功:确认服务器返回值
onSuccess: (result) => {
confirmLocalArticle(articleId, result);
},
// 失败:回滚到之前的状态
onError: (err, draft) => {
rollbackLocalArticle(articleId);
},
});
return (
<button
onClick={() => saveMutation.mutate(currentDraft)}
disabled={saveMutation.isPending}
>
{saveMutation.isPending ? "保存中..." : "保存"}
</button>
);
}
mutation 原语的价值在于:写操作的状态(pending / success / error)和副作用(乐观更新、回滚)被框架统一管理,不再需要开发者手动拼装 createEffect + try/catch + signal reset 的组合。
状态处理的变化与 Breaking Changes
2.0 对状态处理模型做了调整,同时带来了一批 breaking changes。如果你有 1.x 项目,升级前需要重点关注:
| 变化项 | 1.x 行为 | 2.0 行为 | 迁移影响 |
|---|---|---|---|
| 异步数据获取 | createResource 包装 |
Promise 直接消费 | 需要重写数据获取层 |
| Suspense 批处理 | 不确定性,可能闪烁 | 确定性批处理 | 行为变化,但通常是改善 |
| Mutation 操作 | 手动组合 signal + effect | createMutation 原语 |
新 API,旧代码需迁移 |
| 状态原语 API | createSignal / createStore |
调整了部分 API 签名 | 具体签名变化需对照 changelog |
| 组件返回类型 | 组件返回 JSX 元素 | 可能调整了组件函数约束 | 需检查自定义组件 |
升级建议:
- 不要在生产主干上直接切 2.0 Beta——先在独立分支上跑迁移测试
- 优先迁移数据获取层——从
createResource到 Promise 直用的改动量最大,但收益也最明显 - 逐个替换 mutation 逻辑——把手动拼装的写操作状态管理逐步换成
createMutation - 跑完整的 Suspense 嵌套测试——2.0 的确定性批处理改变了 fallback 时序,之前靠"闪烁恰好被容忍"的 UI 可能需要调整
实践检查清单
在决定是否采用 SolidJS 2.0 Beta 时,可以用这个清单快速评估:
- [ ] 项目中
createResource的使用数量和复杂度——数量越多,迁移工作量越大,但异步体验改善也越明显 - [ ] 是否有嵌套 Suspense 场景——如果有,2.0 的确定性批处理是直接收益点
- [ ] 写操作(表单提交、乐观更新)的复杂度——mutation 原语能显著简化这类逻辑
- [ ] 是否依赖第三方 SolidJS 库——生态库可能还没适配 2.0 的 breaking changes,需要逐个确认
- [ ] 团队对细粒度响应式的熟悉程度——2.0 的异步模型更接近原生 JS,但心智模型转换仍需时间
SolidJS 2.0 Beta 的方向很明确:让异步不再是框架的"特殊场景",而是和同步信号一样自然的开发体验,同时保持无虚拟 DOM 的细粒度更新优势。如果你正在构建高交互、数据密集的前端应用,这次重构值得认真跟进。